Go back to index | PHP CodeBrowser

js/tiny_mce/tiny_mce_src.js
  1.     1  (function(win) {
  2.     2   var whiteSpaceRe = /^\s*|\s*$/g,
  3.     3   undefined, isRegExpBroken = 'B'.replace(/A(.)|B/, '$1') === '$1';
  4.     4  
  5.     5   var tinymce = {
  6.     6   majorVersion : '3',
  7.     7  
  8.     8   minorVersion : '4.1',
  9.     9  
  10.    10   releaseDate : '2011-03-24',
  11.    11  
  12.    12   _init : function() {
  13.    13   var t = this, d = document, na = navigator, ua = na.userAgent, i, nl, n, base, p, v;
  14.    14  
  15.    15   t.isOpera = win.opera && opera.buildNumber;
  16.    16  
  17.    17   t.isWebKit = /WebKit/.test(ua);
  18.    18  
  19.    19   t.isIE = !t.isWebKit && !t.isOpera && (/MSIE/gi).test(ua) && (/Explorer/gi).test(na.appName);
  20.    20  
  21.    21   t.isIE6 = t.isIE && /MSIE [56]/.test(ua);
  22.    22  
  23.    23   t.isGecko = !t.isWebKit && /Gecko/.test(ua);
  24.    24  
  25.    25   t.isMac = ua.indexOf('Mac') != -1;
  26.    26  
  27.    27   t.isAir = /adobeair/i.test(ua);
  28.    28  
  29.    29   t.isIDevice = /(iPad|iPhone)/.test(ua);
  30.    30  
  31.    31   // TinyMCE .NET webcontrol might be setting the values for TinyMCE
  32.    32   if (win.tinyMCEPreInit) {
  33.    33   t.suffix = tinyMCEPreInit.suffix;
  34.    34   t.baseURL = tinyMCEPreInit.base;
  35.    35   t.query = tinyMCEPreInit.query;
  36.    36   return;
  37.    37   }
  38.    38  
  39.    39   // Get suffix and base
  40.    40   t.suffix = '';
  41.    41  
  42.    42   // If base element found, add that infront of baseURL
  43.    43   nl = d.getElementsByTagName('base');
  44.    44   for (i=0; i<nl.length; i++) {
  45.    45   if (v = nl[i].href) {
  46.    46   // Host only value like http://site.com or http://site.com:8008
  47.    47   if (/^https?:\/\/[^\/]+$/.test(v))
  48.    48   v += '/';
  49.    49  
  50.    50   base = v ? v.match(/.*\//)[0] : ''; // Get only directory
  51.    51   }
  52.    52   }
  53.    53  
  54.    54   function getBase(n) {
  55.    55   if (n.src && /tiny_mce(|_gzip|_jquery|_prototype|_full)(_dev|_src)?.js/.test(n.src)) {
  56.    56   if (/_(src|dev)\.js/g.test(n.src))
  57.    57   t.suffix = '_src';
  58.    58  
  59.    59   if ((p = n.src.indexOf('?')) != -1)
  60.    60   t.query = n.src.substring(p + 1);
  61.    61  
  62.    62   t.baseURL = n.src.substring(0, n.src.lastIndexOf('/'));
  63.    63  
  64.    64   // If path to script is relative and a base href was found add that one infront
  65.    65   // the src property will always be an absolute one on non IE browsers and IE 8
  66.    66   // so this logic will basically only be executed on older IE versions
  67.    67   if (base && t.baseURL.indexOf('://') == -1 && t.baseURL.indexOf('/') !== 0)
  68.    68   t.baseURL = base + t.baseURL;
  69.    69  
  70.    70   return t.baseURL;
  71.    71   }
  72.    72  
  73.    73   return null;
  74.    74   };
  75.    75  
  76.    76   // Check document
  77.    77   nl = d.getElementsByTagName('script');
  78.    78   for (i=0; i<nl.length; i++) {
  79.    79   if (getBase(nl[i]))
  80.    80   return;
  81.    81   }
  82.    82  
  83.    83   // Check head
  84.    84   n = d.getElementsByTagName('head')[0];
  85.    85   if (n) {
  86.    86   nl = n.getElementsByTagName('script');
  87.    87   for (i=0; i<nl.length; i++) {
  88.    88   if (getBase(nl[i]))
  89.    89   return;
  90.    90   }
  91.    91   }
  92.    92  
  93.    93   return;
  94.    94   },
  95.    95  
  96.    96   is : function(o, t) {
  97.    97   if (!t)
  98.    98   return o !== undefined;
  99.    99  
  100.   100   if (t == 'array' && (o.hasOwnProperty && o instanceof Array))
  101.   101   return true;
  102.   102  
  103.   103   return typeof(o) == t;
  104.   104   },
  105.   105  
  106.   106   makeMap : function(items, delim, map) {
  107.   107   var i;
  108.   108  
  109.   109   items = items || [];
  110.   110   delim = delim || ',';
  111.   111  
  112.   112   if (typeof(items) == "string")
  113.   113   items = items.split(delim);
  114.   114  
  115.   115   map = map || {};
  116.   116  
  117.   117   i = items.length;
  118.   118   while (i--)
  119.   119   map[items[i]] = {};
  120.   120  
  121.   121   return map;
  122.   122   },
  123.   123  
  124.   124   each : function(o, cb, s) {
  125.   125   var n, l;
  126.   126  
  127.   127   if (!o)
  128.   128   return 0;
  129.   129  
  130.   130   s = s || o;
  131.   131  
  132.   132   if (o.length !== undefined) {
  133.   133   // Indexed arrays, needed for Safari
  134.   134   for (n=0, l = o.length; n < l; n++) {
  135.   135   if (cb.call(s, o[n], n, o) === false)
  136.   136   return 0;
  137.   137   }
  138.   138   } else {
  139.   139   // Hashtables
  140.   140   for (n in o) {
  141.   141   if (o.hasOwnProperty(n)) {
  142.   142   if (cb.call(s, o[n], n, o) === false)
  143.   143   return 0;
  144.   144   }
  145.   145   }
  146.   146   }
  147.   147  
  148.   148   return 1;
  149.   149   },
  150.   150  
  151.   151  
  152.   152   map : function(a, f) {
  153.   153   var o = [];
  154.   154  
  155.   155   tinymce.each(a, function(v) {
  156.   156   o.push(f(v));
  157.   157   });
  158.   158  
  159.   159   return o;
  160.   160   },
  161.   161  
  162.   162   grep : function(a, f) {
  163.   163   var o = [];
  164.   164  
  165.   165   tinymce.each(a, function(v) {
  166.   166   if (!f || f(v))
  167.   167   o.push(v);
  168.   168   });
  169.   169  
  170.   170   return o;
  171.   171   },
  172.   172  
  173.   173   inArray : function(a, v) {
  174.   174   var i, l;
  175.   175  
  176.   176   if (a) {
  177.   177   for (i = 0, l = a.length; i < l; i++) {
  178.   178   if (a[i] === v)
  179.   179   return i;
  180.   180   }
  181.   181   }
  182.   182  
  183.   183   return -1;
  184.   184   },
  185.   185  
  186.   186   extend : function(o, e) {
  187.   187   var i, l, a = arguments;
  188.   188  
  189.   189   for (i = 1, l = a.length; i < l; i++) {
  190.   190   e = a[i];
  191.   191  
  192.   192   tinymce.each(e, function(v, n) {
  193.   193   if (v !== undefined)
  194.   194   o[n] = v;
  195.   195   });
  196.   196   }
  197.   197  
  198.   198   return o;
  199.   199   },
  200.   200  
  201.   201  
  202.   202   trim : function(s) {
  203.   203   return (s ? '' + s : '').replace(whiteSpaceRe, '');
  204.   204   },
  205.   205  
  206.   206   create : function(s, p, root) {
  207.   207   var t = this, sp, ns, cn, scn, c, de = 0;
  208.   208  
  209.   209   // Parse : <prefix> <class>:<super class>
  210.   210   s = /^((static) )?([\w.]+)(:([\w.]+))?/.exec(s);
  211.   211   cn = s[3].match(/(^|\.)(\w+)$/i)[2]; // Class name
  212.   212  
  213.   213   // Create namespace for new class
  214.   214   ns = t.createNS(s[3].replace(/\.\w+$/, ''), root);
  215.   215  
  216.   216   // Class already exists
  217.   217   if (ns[cn])
  218.   218   return;
  219.   219  
  220.   220   // Make pure static class
  221.   221   if (s[2] == 'static') {
  222.   222   ns[cn] = p;
  223.   223  
  224.   224   if (this.onCreate)
  225.   225   this.onCreate(s[2], s[3], ns[cn]);
  226.   226  
  227.   227   return;
  228.   228   }
  229.   229  
  230.   230   // Create default constructor
  231.   231   if (!p[cn]) {
  232.   232   p[cn] = function() {};
  233.   233   de = 1;
  234.   234   }
  235.   235  
  236.   236   // Add constructor and methods
  237.   237   ns[cn] = p[cn];
  238.   238   t.extend(ns[cn].prototype, p);
  239.   239  
  240.   240   // Extend
  241.   241   if (s[5]) {
  242.   242   sp = t.resolve(s[5]).prototype;
  243.   243   scn = s[5].match(/\.(\w+)$/i)[1]; // Class name
  244.   244  
  245.   245   // Extend constructor
  246.   246   c = ns[cn];
  247.   247   if (de) {
  248.   248   // Add passthrough constructor
  249.   249   ns[cn] = function() {
  250.   250   return sp[scn].apply(this, arguments);
  251.   251   };
  252.   252   } else {
  253.   253   // Add inherit constructor
  254.   254   ns[cn] = function() {
  255.   255   this.parent = sp[scn];
  256.   256   return c.apply(this, arguments);
  257.   257   };
  258.   258   }
  259.   259   ns[cn].prototype[cn] = ns[cn];
  260.   260  
  261.   261   // Add super methods
  262.   262   t.each(sp, function(f, n) {
  263.   263   ns[cn].prototype[n] = sp[n];
  264.   264   });
  265.   265  
  266.   266   // Add overridden methods
  267.   267   t.each(p, function(f, n) {
  268.   268   // Extend methods if needed
  269.   269   if (sp[n]) {
  270.   270   ns[cn].prototype[n] = function() {
  271.   271   this.parent = sp[n];
  272.   272   return f.apply(this, arguments);
  273.   273   };
  274.   274   } else {
  275.   275   if (n != cn)
  276.   276   ns[cn].prototype[n] = f;
  277.   277   }
  278.   278   });
  279.   279   }
  280.   280  
  281.   281   // Add static methods
  282.   282   t.each(p['static'], function(f, n) {
  283.   283   ns[cn][n] = f;
  284.   284   });
  285.   285  
  286.   286   if (this.onCreate)
  287.   287   this.onCreate(s[2], s[3], ns[cn].prototype);
  288.   288   },
  289.   289  
  290.   290   walk : function(o, f, n, s) {
  291.   291   s = s || this;
  292.   292  
  293.   293   if (o) {
  294.   294   if (n)
  295.   295   o = o[n];
  296.   296  
  297.   297   tinymce.each(o, function(o, i) {
  298.   298   if (f.call(s, o, i, n) === false)
  299.   299   return false;
  300.   300  
  301.   301   tinymce.walk(o, f, n, s);
  302.   302   });
  303.   303   }
  304.   304   },
  305.   305  
  306.   306   createNS : function(n, o) {
  307.   307   var i, v;
  308.   308  
  309.   309   o = o || win;
  310.   310  
  311.   311   n = n.split('.');
  312.   312   for (i=0; i<n.length; i++) {
  313.   313   v = n[i];
  314.   314  
  315.   315   if (!o[v])
  316.   316   o[v] = {};
  317.   317  
  318.   318   o = o[v];
  319.   319   }
  320.   320  
  321.   321   return o;
  322.   322   },
  323.   323  
  324.   324   resolve : function(n, o) {
  325.   325   var i, l;
  326.   326  
  327.   327   o = o || win;
  328.   328  
  329.   329   n = n.split('.');
  330.   330   for (i = 0, l = n.length; i < l; i++) {
  331.   331   o = o[n[i]];
  332.   332  
  333.   333   if (!o)
  334.   334   break;
  335.   335   }
  336.   336  
  337.   337   return o;
  338.   338   },
  339.   339  
  340.   340   addUnload : function(f, s) {
  341.   341   var t = this;
  342.   342  
  343.   343   f = {func : f, scope : s || this};
  344.   344  
  345.   345   if (!t.unloads) {
  346.   346   function unload() {
  347.   347   var li = t.unloads, o, n;
  348.   348  
  349.   349   if (li) {
  350.   350   // Call unload handlers
  351.   351   for (n in li) {
  352.   352   o = li[n];
  353.   353  
  354.   354   if (o && o.func)
  355.   355   o.func.call(o.scope, 1); // Send in one arg to distinct unload and user destroy
  356.   356   }
  357.   357  
  358.   358   // Detach unload function
  359.   359   if (win.detachEvent) {
  360.   360   win.detachEvent('onbeforeunload', fakeUnload);
  361.   361   win.detachEvent('onunload', unload);
  362.   362   } else if (win.removeEventListener)
  363.   363   win.removeEventListener('unload', unload, false);
  364.   364  
  365.   365   // Destroy references
  366.   366   t.unloads = o = li = w = unload = 0;
  367.   367  
  368.   368   // Run garbarge collector on IE
  369.   369   if (win.CollectGarbage)
  370.   370   CollectGarbage();
  371.   371   }
  372.   372   };
  373.   373  
  374.   374   function fakeUnload() {
  375.   375   var d = document;
  376.   376  
  377.   377   // Is there things still loading, then do some magic
  378.   378   if (d.readyState == 'interactive') {
  379.   379   function stop() {
  380.   380   // Prevent memory leak
  381.   381   d.detachEvent('onstop', stop);
  382.   382  
  383.   383   // Call unload handler
  384.   384   if (unload)
  385.   385   unload();
  386.   386  
  387.   387   d = 0;
  388.   388   };
  389.   389  
  390.   390   // Fire unload when the currently loading page is stopped
  391.   391   if (d)
  392.   392   d.attachEvent('onstop', stop);
  393.   393  
  394.   394   // Remove onstop listener after a while to prevent the unload function
  395.   395   // to execute if the user presses cancel in an onbeforeunload
  396.   396   // confirm dialog and then presses the browser stop button
  397.   397   win.setTimeout(function() {
  398.   398   if (d)
  399.   399   d.detachEvent('onstop', stop);
  400.   400   }, 0);
  401.   401   }
  402.   402   };
  403.   403  
  404.   404   // Attach unload handler
  405.   405   if (win.attachEvent) {
  406.   406   win.attachEvent('onunload', unload);
  407.   407   win.attachEvent('onbeforeunload', fakeUnload);
  408.   408   } else if (win.addEventListener)
  409.   409   win.addEventListener('unload', unload, false);
  410.   410  
  411.   411   // Setup initial unload handler array
  412.   412   t.unloads = [f];
  413.   413   } else
  414.   414   t.unloads.push(f);
  415.   415  
  416.   416   return f;
  417.   417   },
  418.   418  
  419.   419   removeUnload : function(f) {
  420.   420   var u = this.unloads, r = null;
  421.   421  
  422.   422   tinymce.each(u, function(o, i) {
  423.   423   if (o && o.func == f) {
  424.   424   u.splice(i, 1);
  425.   425   r = f;
  426.   426   return false;
  427.   427   }
  428.   428   });
  429.   429  
  430.   430   return r;
  431.   431   },
  432.   432  
  433.   433   explode : function(s, d) {
  434.   434   return s ? tinymce.map(s.split(d || ','), tinymce.trim) : s;
  435.   435   },
  436.   436  
  437.   437   _addVer : function(u) {
  438.   438   var v;
  439.   439  
  440.   440   if (!this.query)
  441.   441   return u;
  442.   442  
  443.   443   v = (u.indexOf('?') == -1 ? '?' : '&') + this.query;
  444.   444  
  445.   445   if (u.indexOf('#') == -1)
  446.   446   return u + v;
  447.   447  
  448.   448   return u.replace('#', v + '#');
  449.   449   },
  450.   450  
  451.   451   // Fix function for IE 9 where regexps isn't working correctly
  452.   452   // Todo: remove me once MS fixes the bug
  453.   453   _replace : function(find, replace, str) {
  454.   454   // On IE9 we have to fake $x replacement
  455.   455   if (isRegExpBroken) {
  456.   456   return str.replace(find, function() {
  457.   457   var val = replace, args = arguments, i;
  458.   458  
  459.   459   for (i = 0; i < args.length - 2; i++) {
  460.   460   if (args[i] === undefined) {
  461.   461   val = val.replace(new RegExp('\\$' + i, 'g'), '');
  462.   462   } else {
  463.   463   val = val.replace(new RegExp('\\$' + i, 'g'), args[i]);
  464.   464   }
  465.   465   }
  466.   466  
  467.   467   return val;
  468.   468   });
  469.   469   }
  470.   470  
  471.   471   return str.replace(find, replace);
  472.   472   }
  473.   473  
  474.   474   };
  475.   475  
  476.   476   // Initialize the API
  477.   477   tinymce._init();
  478.   478  
  479.   479   // Expose tinymce namespace to the global namespace (window)
  480.   480   win.tinymce = win.tinyMCE = tinymce;
  481.   481  
  482.   482   // Describe the different namespaces
  483.   483  
  484.   484   })(window);
  485.   485  
  486.   486  
  487.   487  tinymce.create('tinymce.util.Dispatcher', {
  488.   488   scope : null,
  489.   489   listeners : null,
  490.   490  
  491.   491   Dispatcher : function(s) {
  492.   492   this.scope = s || this;
  493.   493   this.listeners = [];
  494.   494   },
  495.   495  
  496.   496   add : function(cb, s) {
  497.   497   this.listeners.push({cb : cb, scope : s || this.scope});
  498.   498  
  499.   499   return cb;
  500.   500   },
  501.   501  
  502.   502   addToTop : function(cb, s) {
  503.   503   this.listeners.unshift({cb : cb, scope : s || this.scope});
  504.   504  
  505.   505   return cb;
  506.   506   },
  507.   507  
  508.   508   remove : function(cb) {
  509.   509   var l = this.listeners, o = null;
  510.   510  
  511.   511   tinymce.each(l, function(c, i) {
  512.   512   if (cb == c.cb) {
  513.   513   o = cb;
  514.   514   l.splice(i, 1);
  515.   515   return false;
  516.   516   }
  517.   517   });
  518.   518  
  519.   519   return o;
  520.   520   },
  521.   521  
  522.   522   dispatch : function() {
  523.   523   var s, a = arguments, i, li = this.listeners, c;
  524.   524  
  525.   525   // Needs to be a real loop since the listener count might change while looping
  526.   526   // And this is also more efficient
  527.   527   for (i = 0; i<li.length; i++) {
  528.   528   c = li[i];
  529.   529   s = c.cb.apply(c.scope, a);
  530.   530  
  531.   531   if (s === false)
  532.   532   break;
  533.   533   }
  534.   534  
  535.   535   return s;
  536.   536   }
  537.   537  
  538.   538   });
  539.   539  
  540.   540  (function() {
  541.   541   var each = tinymce.each;
  542.   542  
  543.   543   tinymce.create('tinymce.util.URI', {
  544.   544   URI : function(u, s) {
  545.   545   var t = this, o, a, b;
  546.   546  
  547.   547   // Trim whitespace
  548.   548   u = tinymce.trim(u);
  549.   549  
  550.   550   // Default settings
  551.   551   s = t.settings = s || {};
  552.   552  
  553.   553   // Strange app protocol or local anchor
  554.   554   if (/^(mailto|tel|news|javascript|about|data):/i.test(u) || /^\s*#/.test(u)) {
  555.   555   t.source = u;
  556.   556   return;
  557.   557   }
  558.   558  
  559.   559   // Absolute path with no host, fake host and protocol
  560.   560   if (u.indexOf('/') === 0 && u.indexOf('//') !== 0)
  561.   561   u = (s.base_uri ? s.base_uri.protocol || 'http' : 'http') + '://mce_host' + u;
  562.   562  
  563.   563   // Relative path http:// or protocol relative //path
  564.   564   if (!/^\w*:?\/\//.test(u))
  565.   565   u = (s.base_uri.protocol || 'http') + '://mce_host' + t.toAbsPath(s.base_uri.path, u);
  566.   566  
  567.   567   // Parse URL (Credits goes to Steave, http://blog.stevenlevithan.com/archives/parseuri)
  568.   568   u = u.replace(/@@/g, '(mce_at)'); // Zope 3 workaround, they use @@something
  569.   569   u = /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/.exec(u);
  570.   570   each(["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"], function(v, i) {
  571.   571   var s = u[i];
  572.   572  
  573.   573   // Zope 3 workaround, they use @@something
  574.   574   if (s)
  575.   575   s = s.replace(/\(mce_at\)/g, '@@');
  576.   576  
  577.   577   t[v] = s;
  578.   578   });
  579.   579  
  580.   580   if (b = s.base_uri) {
  581.   581   if (!t.protocol)
  582.   582   t.protocol = b.protocol;
  583.   583  
  584.   584   if (!t.userInfo)
  585.   585   t.userInfo = b.userInfo;
  586.   586  
  587.   587   if (!t.port && t.host == 'mce_host')
  588.   588   t.port = b.port;
  589.   589  
  590.   590   if (!t.host || t.host == 'mce_host')
  591.   591   t.host = b.host;
  592.   592  
  593.   593   t.source = '';
  594.   594   }
  595.   595  
  596.   596   //t.path = t.path || '/';
  597.   597   },
  598.   598  
  599.   599   setPath : function(p) {
  600.   600   var t = this;
  601.   601  
  602.   602   p = /^(.*?)\/?(\w+)?$/.exec(p);
  603.   603  
  604.   604   // Update path parts
  605.   605   t.path = p[0];
  606.   606   t.directory = p[1];
  607.   607   t.file = p[2];
  608.   608  
  609.   609   // Rebuild source
  610.   610   t.source = '';
  611.   611   t.getURI();
  612.   612   },
  613.   613  
  614.   614   toRelative : function(u) {
  615.   615   var t = this, o;
  616.   616  
  617.   617   if (u === "./")
  618.   618   return u;
  619.   619  
  620.   620   u = new tinymce.util.URI(u, {base_uri : t});
  621.   621  
  622.   622   // Not on same domain/port or protocol
  623.   623   if ((u.host != 'mce_host' && t.host != u.host && u.host) || t.port != u.port || t.protocol != u.protocol)
  624.   624   return u.getURI();
  625.   625  
  626.   626   o = t.toRelPath(t.path, u.path);
  627.   627  
  628.   628   // Add query
  629.   629   if (u.query)
  630.   630   o += '?' + u.query;
  631.   631  
  632.   632   // Add anchor
  633.   633   if (u.anchor)
  634.   634   o += '#' + u.anchor;
  635.   635  
  636.   636   return o;
  637.   637   },
  638.   638  
  639.   639   toAbsolute : function(u, nh) {
  640.   640   var u = new tinymce.util.URI(u, {base_uri : this});
  641.   641  
  642.   642   return u.getURI(this.host == u.host && this.protocol == u.protocol ? nh : 0);
  643.   643   },
  644.   644  
  645.   645   toRelPath : function(base, path) {
  646.   646   var items, bp = 0, out = '', i, l;
  647.   647  
  648.   648   // Split the paths
  649.   649   base = base.substring(0, base.lastIndexOf('/'));
  650.   650   base = base.split('/');
  651.   651   items = path.split('/');
  652.   652  
  653.   653   if (base.length >= items.length) {
  654.   654   for (i = 0, l = base.length; i < l; i++) {
  655.   655   if (i >= items.length || base[i] != items[i]) {
  656.   656   bp = i + 1;
  657.   657   break;
  658.   658   }
  659.   659   }
  660.   660   }
  661.   661  
  662.   662   if (base.length < items.length) {
  663.   663   for (i = 0, l = items.length; i < l; i++) {
  664.   664   if (i >= base.length || base[i] != items[i]) {
  665.   665   bp = i + 1;
  666.   666   break;
  667.   667   }
  668.   668   }
  669.   669   }
  670.   670  
  671.   671   if (bp == 1)
  672.   672   return path;
  673.   673  
  674.   674   for (i = 0, l = base.length - (bp - 1); i < l; i++)
  675.   675   out += "../";
  676.   676  
  677.   677   for (i = bp - 1, l = items.length; i < l; i++) {
  678.   678   if (i != bp - 1)
  679.   679   out += "/" + items[i];
  680.   680   else
  681.   681   out += items[i];
  682.   682   }
  683.   683  
  684.   684   return out;
  685.   685   },
  686.   686  
  687.   687   toAbsPath : function(base, path) {
  688.   688   var i, nb = 0, o = [], tr, outPath;
  689.   689  
  690.   690   // Split paths
  691.   691   tr = /\/$/.test(path) ? '/' : '';
  692.   692   base = base.split('/');
  693.   693   path = path.split('/');
  694.   694  
  695.   695   // Remove empty chunks
  696.   696   each(base, function(k) {
  697.   697   if (k)
  698.   698   o.push(k);
  699.   699   });
  700.   700  
  701.   701   base = o;
  702.   702  
  703.   703   // Merge relURLParts chunks
  704.   704   for (i = path.length - 1, o = []; i >= 0; i--) {
  705.   705   // Ignore empty or .
  706.   706   if (path[i].length == 0 || path[i] == ".")
  707.   707   continue;
  708.   708  
  709.   709   // Is parent
  710.   710   if (path[i] == '..') {
  711.   711   nb++;
  712.   712   continue;
  713.   713   }
  714.   714  
  715.   715   // Move up
  716.   716   if (nb > 0) {
  717.   717   nb--;
  718.   718   continue;
  719.   719   }
  720.   720  
  721.   721   o.push(path[i]);
  722.   722   }
  723.   723  
  724.   724   i = base.length - nb;
  725.   725  
  726.   726   // If /a/b/c or /
  727.   727   if (i <= 0)
  728.   728   outPath = o.reverse().join('/');
  729.   729   else
  730.   730   outPath = base.slice(0, i).join('/') + '/' + o.reverse().join('/');
  731.   731  
  732.   732   // Add front / if it's needed
  733.   733   if (outPath.indexOf('/') !== 0)
  734.   734   outPath = '/' + outPath;
  735.   735  
  736.   736   // Add traling / if it's needed
  737.   737   if (tr && outPath.lastIndexOf('/') !== outPath.length - 1)
  738.   738   outPath += tr;
  739.   739  
  740.   740   return outPath;
  741.   741   },
  742.   742  
  743.   743   getURI : function(nh) {
  744.   744   var s, t = this;
  745.   745  
  746.   746   // Rebuild source
  747.   747   if (!t.source || nh) {
  748.   748   s = '';
  749.   749  
  750.   750   if (!nh) {
  751.   751   if (t.protocol)
  752.   752   s += t.protocol + '://';
  753.   753  
  754.   754   if (t.userInfo)
  755.   755   s += t.userInfo + '@';
  756.   756  
  757.   757   if (t.host)
  758.   758   s += t.host;
  759.   759  
  760.   760   if (t.port)
  761.   761   s += ':' + t.port;
  762.   762   }
  763.   763  
  764.   764   if (t.path)
  765.   765   s += t.path;
  766.   766  
  767.   767   if (t.query)
  768.   768   s += '?' + t.query;
  769.   769  
  770.   770   if (t.anchor)
  771.   771   s += '#' + t.anchor;
  772.   772  
  773.   773   t.source = s;
  774.   774   }
  775.   775  
  776.   776   return t.source;
  777.   777   }
  778.   778   });
  779.   779  })();
  780.   780  
  781.   781  (function() {
  782.   782   var each = tinymce.each;
  783.   783  
  784.   784   tinymce.create('static tinymce.util.Cookie', {
  785.   785   getHash : function(n) {
  786.   786   var v = this.get(n), h;
  787.   787  
  788.   788   if (v) {
  789.   789   each(v.split('&'), function(v) {
  790.   790   v = v.split('=');
  791.   791   h = h || {};
  792.   792   h[unescape(v[0])] = unescape(v[1]);
  793.   793   });
  794.   794   }
  795.   795  
  796.   796   return h;
  797.   797   },
  798.   798  
  799.   799   setHash : function(n, v, e, p, d, s) {
  800.   800   var o = '';
  801.   801  
  802.   802   each(v, function(v, k) {
  803.   803   o += (!o ? '' : '&') + escape(k) + '=' + escape(v);
  804.   804   });
  805.   805  
  806.   806   this.set(n, o, e, p, d, s);
  807.   807   },
  808.   808  
  809.   809   get : function(n) {
  810.   810   var c = document.cookie, e, p = n + "=", b;
  811.   811  
  812.   812   // Strict mode
  813.   813   if (!c)
  814.   814   return;
  815.   815  
  816.   816   b = c.indexOf("; " + p);
  817.   817  
  818.   818   if (b == -1) {
  819.   819   b = c.indexOf(p);
  820.   820  
  821.   821   if (b != 0)
  822.   822   return null;
  823.   823   } else
  824.   824   b += 2;
  825.   825  
  826.   826   e = c.indexOf(";", b);
  827.   827  
  828.   828   if (e == -1)
  829.   829   e = c.length;
  830.   830  
  831.   831   return unescape(c.substring(b + p.length, e));
  832.   832   },
  833.   833  
  834.   834   set : function(n, v, e, p, d, s) {
  835.   835   document.cookie = n + "=" + escape(v) +
  836.   836   ((e) ? "; expires=" + e.toGMTString() : "") +
  837.   837   ((p) ? "; path=" + escape(p) : "") +
  838.   838   ((d) ? "; domain=" + d : "") +
  839.   839   ((s) ? "; secure" : "");
  840.   840   },
  841.   841  
  842.   842   remove : function(n, p) {
  843.   843   var d = new Date();
  844.   844  
  845.   845   d.setTime(d.getTime() - 1000);
  846.   846  
  847.   847   this.set(n, '', d, p, d);
  848.   848   }
  849.   849   });
  850.   850  })();
  851.   851  
  852.   852  (function() {
  853.   853   function serialize(o, quote) {
  854.   854   var i, v, t;
  855.   855  
  856.   856   quote = quote || '"';
  857.   857  
  858.   858   if (o == null)
  859.   859   return 'null';
  860.   860  
  861.   861   t = typeof o;
  862.   862  
  863.   863   if (t == 'string') {
  864.   864   v = '\bb\tt\nn\ff\rr\""\'\'\\\\';
  865.   865  
  866.   866   return quote + o.replace(/([\u0080-\uFFFF\x00-\x1f\"\'\\])/g, function(a, b) {
  867.   867   // Make sure single quotes never get encoded inside double quotes for JSON compatibility
  868.   868   if (quote === '"' && a === "'")
  869.   869   return a;
  870.   870  
  871.   871   i = v.indexOf(b);
  872.   872  
  873.   873   if (i + 1)
  874.   874   return '\\' + v.charAt(i + 1);
  875.   875  
  876.   876   a = b.charCodeAt().toString(16);
  877.   877  
  878.   878   return '\\u' + '0000'.substring(a.length) + a;
  879.   879   }) + quote;
  880.   880   }
  881.   881  
  882.   882   if (t == 'object') {
  883.   883   if (o.hasOwnProperty && o instanceof Array) {
  884.   884   for (i=0, v = '['; i<o.length; i++)
  885.   885   v += (i > 0 ? ',' : '') + serialize(o[i], quote);
  886.   886  
  887.   887   return v + ']';
  888.   888   }
  889.   889  
  890.   890   v = '{';
  891.   891  
  892.   892   for (i in o)
  893.   893   v += typeof o[i] != 'function' ? (v.length > 1 ? ',' + quote : quote) + i + quote +':' + serialize(o[i], quote) : '';
  894.   894  
  895.   895   return v + '}';
  896.   896   }
  897.   897  
  898.   898   return '' + o;
  899.   899   };
  900.   900  
  901.   901   tinymce.util.JSON = {
  902.   902   serialize: serialize,
  903.   903  
  904.   904   parse: function(s) {
  905.   905   try {
  906.   906   return eval('(' + s + ')');
  907.   907   } catch (ex) {
  908.   908   // Ignore
  909.   909   }
  910.   910   }
  911.   911  
  912.   912   };
  913.   913  })();
  914.   914  tinymce.create('static tinymce.util.XHR', {
  915.   915   send : function(o) {
  916.   916   var x, t, w = window, c = 0;
  917.   917  
  918.   918   // Default settings
  919.   919   o.scope = o.scope || this;
  920.   920   o.success_scope = o.success_scope || o.scope;
  921.   921   o.error_scope = o.error_scope || o.scope;
  922.   922   o.async = o.async === false ? false : true;
  923.   923   o.data = o.data || '';
  924.   924  
  925.   925   function get(s) {
  926.   926   x = 0;
  927.   927  
  928.   928   try {
  929.   929   x = new ActiveXObject(s);
  930.   930   } catch (ex) {
  931.   931   }
  932.   932  
  933.   933   return x;
  934.   934   };
  935.   935  
  936.   936   x = w.XMLHttpRequest ? new XMLHttpRequest() : get('Microsoft.XMLHTTP') || get('Msxml2.XMLHTTP');
  937.   937  
  938.   938   if (x) {
  939.   939   if (x.overrideMimeType)
  940.   940   x.overrideMimeType(o.content_type);
  941.   941  
  942.   942   x.open(o.type || (o.data ? 'POST' : 'GET'), o.url, o.async);
  943.   943  
  944.   944   if (o.content_type)
  945.   945   x.setRequestHeader('Content-Type', o.content_type);
  946.   946  
  947.   947   x.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
  948.   948  
  949.   949   x.send(o.data);
  950.   950  
  951.   951   function ready() {
  952.   952   if (!o.async || x.readyState == 4 || c++ > 10000) {
  953.   953   if (o.success && c < 10000 && x.status == 200)
  954.   954   o.success.call(o.success_scope, '' + x.responseText, x, o);
  955.   955   else if (o.error)
  956.   956   o.error.call(o.error_scope, c > 10000 ? 'TIMED_OUT' : 'GENERAL', x, o);
  957.   957  
  958.   958   x = null;
  959.   959   } else
  960.   960   w.setTimeout(ready, 10);
  961.   961   };
  962.   962  
  963.   963   // Syncronous request
  964.   964   if (!o.async)
  965.   965   return ready();
  966.   966  
  967.   967   // Wait for response, onReadyStateChange can not be used since it leaks memory in IE
  968.   968   t = w.setTimeout(ready, 10);
  969.   969   }
  970.   970   }
  971.   971  });
  972.   972  
  973.   973  (function() {
  974.   974   var extend = tinymce.extend, JSON = tinymce.util.JSON, XHR = tinymce.util.XHR;
  975.   975  
  976.   976   tinymce.create('tinymce.util.JSONRequest', {
  977.   977   JSONRequest : function(s) {
  978.   978   this.settings = extend({
  979.   979   }, s);
  980.   980   this.count = 0;
  981.   981   },
  982.   982  
  983.   983   send : function(o) {
  984.   984   var ecb = o.error, scb = o.success;
  985.   985  
  986.   986   o = extend(this.settings, o);
  987.   987  
  988.   988   o.success = function(c, x) {
  989.   989   c = JSON.parse(c);
  990.   990  
  991.   991   if (typeof(c) == 'undefined') {
  992.   992   c = {
  993.   993   error : 'JSON Parse error.'
  994.   994   };
  995.   995   }
  996.   996  
  997.   997   if (c.error)
  998.   998   ecb.call(o.error_scope || o.scope, c.error, x);
  999.   999   else
  1000.  1000   scb.call(o.success_scope || o.scope, c.result);
  1001.  1001   };
  1002.  1002  
  1003.  1003   o.error = function(ty, x) {
  1004.  1004   if (ecb)
  1005.  1005   ecb.call(o.error_scope || o.scope, ty, x);
  1006.  1006   };
  1007.  1007  
  1008.  1008   o.data = JSON.serialize({
  1009.  1009   id : o.id || 'c' + (this.count++),
  1010.  1010   method : o.method,
  1011.  1011   params : o.params
  1012.  1012   });
  1013.  1013  
  1014.  1014   // JSON content type for Ruby on rails. Bug: #1883287
  1015.  1015   o.content_type = 'application/json';
  1016.  1016  
  1017.  1017   XHR.send(o);
  1018.  1018   },
  1019.  1019  
  1020.  1020   'static' : {
  1021.  1021   sendRPC : function(o) {
  1022.  1022   return new tinymce.util.JSONRequest().send(o);
  1023.  1023   }
  1024.  1024   }
  1025.  1025   });
  1026.  1026  }());
  1027.  1027  (function(tinymce) {
  1028.  1028   var namedEntities, baseEntities, reverseEntities,
  1029.  1029   attrsCharsRegExp = /[&\"\u007E-\uD7FF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
  1030.  1030   textCharsRegExp = /[<>&\u007E-\uD7FF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
  1031.  1031   rawCharsRegExp = /[<>&\"\']/g,
  1032.  1032   entityRegExp = /&(#)?([\w]+);/g,
  1033.  1033   asciiMap = {
  1034.  1034   128 : "\u20AC", 130 : "\u201A", 131 : "\u0192", 132 : "\u201E", 133 : "\u2026", 134 : "\u2020",
  1035.  1035   135 : "\u2021", 136 : "\u02C6", 137 : "\u2030", 138 : "\u0160", 139 : "\u2039", 140 : "\u0152",
  1036.  1036   142 : "\u017D", 145 : "\u2018", 146 : "\u2019", 147 : "\u201C", 148 : "\u201D", 149 : "\u2022",
  1037.  1037   150 : "\u2013", 151 : "\u2014", 152 : "\u02DC", 153 : "\u2122", 154 : "\u0161", 155 : "\u203A",
  1038.  1038   156 : "\u0153", 158 : "\u017E", 159 : "\u0178"
  1039.  1039   };
  1040.  1040  
  1041.  1041   // Raw entities
  1042.  1042   baseEntities = {
  1043.  1043   '"' : '&quot;',
  1044.  1044   "'" : '&#39;',
  1045.  1045   '<' : '&lt;',
  1046.  1046   '>' : '&gt;',
  1047.  1047   '&' : '&amp;'
  1048.  1048   };
  1049.  1049  
  1050.  1050   // Reverse lookup table for raw entities
  1051.  1051   reverseEntities = {
  1052.  1052   '&lt;' : '<',
  1053.  1053   '&gt;' : '>',
  1054.  1054   '&amp;' : '&',
  1055.  1055   '&quot;' : '"',
  1056.  1056   '&apos;' : "'"
  1057.  1057   };
  1058.  1058  
  1059.  1059   // Decodes text by using the browser
  1060.  1060   function nativeDecode(text) {
  1061.  1061   var elm;
  1062.  1062  
  1063.  1063   elm = document.createElement("div");
  1064.  1064   elm.innerHTML = text;
  1065.  1065  
  1066.  1066   return elm.textContent || elm.innerText || text;
  1067.  1067   };
  1068.  1068  
  1069.  1069   // Build a two way lookup table for the entities
  1070.  1070   function buildEntitiesLookup(items, radix) {
  1071.  1071   var i, chr, entity, lookup = {};
  1072.  1072  
  1073.  1073   if (items) {
  1074.  1074   items = items.split(',');
  1075.  1075   radix = radix || 10;
  1076.  1076  
  1077.  1077   // Build entities lookup table
  1078.  1078   for (i = 0; i < items.length; i += 2) {
  1079.  1079   chr = String.fromCharCode(parseInt(items[i], radix));
  1080.  1080  
  1081.  1081   // Only add non base entities
  1082.  1082   if (!baseEntities[chr]) {
  1083.  1083   entity = '&' + items[i + 1] + ';';
  1084.  1084   lookup[chr] = entity;
  1085.  1085   lookup[entity] = chr;
  1086.  1086   }
  1087.  1087   }
  1088.  1088  
  1089.  1089   return lookup;
  1090.  1090   }
  1091.  1091   };
  1092.  1092  
  1093.  1093   // Unpack entities lookup where the numbers are in radix 32 to reduce the size
  1094.  1094   namedEntities = buildEntitiesLookup(
  1095.  1095   '50,nbsp,51,iexcl,52,cent,53,pound,54,curren,55,yen,56,brvbar,57,sect,58,uml,59,copy,' +
  1096.  1096   '5a,ordf,5b,laquo,5c,not,5d,shy,5e,reg,5f,macr,5g,deg,5h,plusmn,5i,sup2,5j,sup3,5k,acute,' +
  1097.  1097   '5l,micro,5m,para,5n,middot,5o,cedil,5p,sup1,5q,ordm,5r,raquo,5s,frac14,5t,frac12,5u,frac34,' +
  1098.  1098   '5v,iquest,60,Agrave,61,Aacute,62,Acirc,63,Atilde,64,Auml,65,Aring,66,AElig,67,Ccedil,' +
  1099.  1099   '68,Egrave,69,Eacute,6a,Ecirc,6b,Euml,6c,Igrave,6d,Iacute,6e,Icirc,6f,Iuml,6g,ETH,6h,Ntilde,' +
  1100.  1100   '6i,Ograve,6j,Oacute,6k,Ocirc,6l,Otilde,6m,Ouml,6n,times,6o,Oslash,6p,Ugrave,6q,Uacute,' +
  1101.  1101   '6r,Ucirc,6s,Uuml,6t,Yacute,6u,THORN,6v,szlig,70,agrave,71,aacute,72,acirc,73,atilde,74,auml,' +
  1102.  1102   '75,aring,76,aelig,77,ccedil,78,egrave,79,eacute,7a,ecirc,7b,euml,7c,igrave,7d,iacute,7e,icirc,' +
  1103.  1103   '7f,iuml,7g,eth,7h,ntilde,7i,ograve,7j,oacute,7k,ocirc,7l,otilde,7m,ouml,7n,divide,7o,oslash,' +
  1104.  1104   '7p,ugrave,7q,uacute,7r,ucirc,7s,uuml,7t,yacute,7u,thorn,7v,yuml,ci,fnof,sh,Alpha,si,Beta,' +
  1105.  1105   'sj,Gamma,sk,Delta,sl,Epsilon,sm,Zeta,sn,Eta,so,Theta,sp,Iota,sq,Kappa,sr,Lambda,ss,Mu,' +
  1106.  1106   'st,Nu,su,Xi,sv,Omicron,t0,Pi,t1,Rho,t3,Sigma,t4,Tau,t5,Upsilon,t6,Phi,t7,Chi,t8,Psi,' +
  1107.  1107   't9,Omega,th,alpha,ti,beta,tj,gamma,tk,delta,tl,epsilon,tm,zeta,tn,eta,to,theta,tp,iota,' +
  1108.  1108   'tq,kappa,tr,lambda,ts,mu,tt,nu,tu,xi,tv,omicron,u0,pi,u1,rho,u2,sigmaf,u3,sigma,u4,tau,' +
  1109.  1109   'u5,upsilon,u6,phi,u7,chi,u8,psi,u9,omega,uh,thetasym,ui,upsih,um,piv,812,bull,816,hellip,' +
  1110.  1110   '81i,prime,81j,Prime,81u,oline,824,frasl,88o,weierp,88h,image,88s,real,892,trade,89l,alefsym,' +
  1111.  1111   '8cg,larr,8ch,uarr,8ci,rarr,8cj,darr,8ck,harr,8dl,crarr,8eg,lArr,8eh,uArr,8ei,rArr,8ej,dArr,' +
  1112.  1112   '8ek,hArr,8g0,forall,8g2,part,8g3,exist,8g5,empty,8g7,nabla,8g8,isin,8g9,notin,8gb,ni,8gf,prod,' +
  1113.  1113   '8gh,sum,8gi,minus,8gn,lowast,8gq,radic,8gt,prop,8gu,infin,8h0,ang,8h7,and,8h8,or,8h9,cap,8ha,cup,' +
  1114.  1114   '8hb,int,8hk,there4,8hs,sim,8i5,cong,8i8,asymp,8j0,ne,8j1,equiv,8j4,le,8j5,ge,8k2,sub,8k3,sup,8k4,' +
  1115.  1115   'nsub,8k6,sube,8k7,supe,8kl,oplus,8kn,otimes,8l5,perp,8m5,sdot,8o8,lceil,8o9,rceil,8oa,lfloor,8ob,' +
  1116.  1116   'rfloor,8p9,lang,8pa,rang,9ea,loz,9j0,spades,9j3,clubs,9j5,hearts,9j6,diams,ai,OElig,aj,oelig,b0,' +
  1117.  1117   'Scaron,b1,scaron,bo,Yuml,m6,circ,ms,tilde,802,ensp,803,emsp,809,thinsp,80c,zwnj,80d,zwj,80e,lrm,' +
  1118.  1118   '80f,rlm,80j,ndash,80k,mdash,80o,lsquo,80p,rsquo,80q,sbquo,80s,ldquo,80t,rdquo,80u,bdquo,810,dagger,' +
  1119.  1119   '811,Dagger,81g,permil,81p,lsaquo,81q,rsaquo,85c,euro'
  1120.  1120   , 32);
  1121.  1121  
  1122.  1122   tinymce.html = tinymce.html || {};
  1123.  1123  
  1124.  1124   tinymce.html.Entities = {
  1125.  1125   encodeRaw : function(text, attr) {
  1126.  1126   return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
  1127.  1127   return baseEntities[chr] || chr;
  1128.  1128   });
  1129.  1129   },
  1130.  1130  
  1131.  1131   encodeAllRaw : function(text) {
  1132.  1132   return ('' + text).replace(rawCharsRegExp, function(chr) {
  1133.  1133   return baseEntities[chr] || chr;
  1134.  1134   });
  1135.  1135   },
  1136.  1136  
  1137.  1137   encodeNumeric : function(text, attr) {
  1138.  1138   return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
  1139.  1139   // Multi byte sequence convert it to a single entity
  1140.  1140   if (chr.length > 1)
  1141.  1141   return '&#' + (((chr.charCodeAt(0) - 0xD800) * 0x400) + (chr.charCodeAt(1) - 0xDC00) + 0x10000) + ';';
  1142.  1142  
  1143.  1143   return baseEntities[chr] || '&#' + chr.charCodeAt(0) + ';';
  1144.  1144   });
  1145.  1145   },
  1146.  1146  
  1147.  1147   encodeNamed : function(text, attr, entities) {
  1148.  1148   entities = entities || namedEntities;
  1149.  1149  
  1150.  1150   return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
  1151.  1151   return baseEntities[chr] || entities[chr] || chr;
  1152.  1152   });
  1153.  1153   },
  1154.  1154  
  1155.  1155   getEncodeFunc : function(name, entities) {
  1156.  1156   var Entities = tinymce.html.Entities;
  1157.  1157  
  1158.  1158   entities = buildEntitiesLookup(entities) || namedEntities;
  1159.  1159  
  1160.  1160   function encodeNamedAndNumeric(text, attr) {
  1161.  1161   return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
  1162.  1162   return baseEntities[chr] || entities[chr] || '&#' + chr.charCodeAt(0) + ';' || chr;
  1163.  1163   });
  1164.  1164   };
  1165.  1165  
  1166.  1166   function encodeCustomNamed(text, attr) {
  1167.  1167   return Entities.encodeNamed(text, attr, entities);
  1168.  1168   };
  1169.  1169  
  1170.  1170   // Replace + with , to be compatible with previous TinyMCE versions
  1171.  1171   name = tinymce.makeMap(name.replace(/\+/g, ','));
  1172.  1172  
  1173.  1173   // Named and numeric encoder
  1174.  1174   if (name.named && name.numeric)
  1175.  1175   return encodeNamedAndNumeric;
  1176.  1176  
  1177.  1177   // Named encoder
  1178.  1178   if (name.named) {
  1179.  1179   // Custom names
  1180.  1180   if (entities)
  1181.  1181   return encodeCustomNamed;
  1182.  1182  
  1183.  1183   return Entities.encodeNamed;
  1184.  1184   }
  1185.  1185  
  1186.  1186   // Numeric
  1187.  1187   if (name.numeric)
  1188.  1188   return Entities.encodeNumeric;
  1189.  1189  
  1190.  1190   // Raw encoder
  1191.  1191   return Entities.encodeRaw;
  1192.  1192   },
  1193.  1193  
  1194.  1194   decode : function(text) {
  1195.  1195   return text.replace(entityRegExp, function(all, numeric, value) {
  1196.  1196   if (numeric) {
  1197.  1197   value = parseInt(value);
  1198.  1198  
  1199.  1199   // Support upper UTF
  1200.  1200   if (value > 0xFFFF) {
  1201.  1201   value -= 0x10000;
  1202.  1202  
  1203.  1203   return String.fromCharCode(0xD800 + (value >> 10), 0xDC00 + (value & 0x3FF));
  1204.  1204   } else
  1205.  1205   return asciiMap[value] || String.fromCharCode(value);
  1206.  1206   }
  1207.  1207  
  1208.  1208   return reverseEntities[all] || namedEntities[all] || nativeDecode(all);
  1209.  1209   });
  1210.  1210   }
  1211.  1211   };
  1212.  1212  })(tinymce);
  1213.  1213  
  1214.  1214  tinymce.html.Styles = function(settings, schema) {
  1215.  1215   var rgbRegExp = /rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)/gi,
  1216.  1216   urlOrStrRegExp = /(?:url(?:(?:\(\s*\"([^\"]+)\"\s*\))|(?:\(\s*\'([^\']+)\'\s*\))|(?:\(\s*([^)\s]+)\s*\))))|(?:\'([^\']+)\')|(?:\"([^\"]+)\")/gi,
  1217.  1217   styleRegExp = /\s*([^:]+):\s*([^;]+);?/g,
  1218.  1218   trimRightRegExp = /\s+$/,
  1219.  1219   urlColorRegExp = /rgb/,
  1220.  1220   undef, i, encodingLookup = {}, encodingItems;
  1221.  1221  
  1222.  1222   settings = settings || {};
  1223.  1223  
  1224.  1224   encodingItems = '\\" \\\' \\; \\: ; : _'.split(' ');
  1225.  1225   for (i = 0; i < encodingItems.length; i++) {
  1226.  1226   encodingLookup[encodingItems[i]] = '_' + i;
  1227.  1227   encodingLookup['_' + i] = encodingItems[i];
  1228.  1228   }
  1229.  1229  
  1230.  1230   function toHex(match, r, g, b) {
  1231.  1231   function hex(val) {
  1232.  1232   val = parseInt(val).toString(16);
  1233.  1233  
  1234.  1234   return val.length > 1 ? val : '0' + val; // 0 -> 00
  1235.  1235   };
  1236.  1236  
  1237.  1237   return '#' + hex(r) + hex(g) + hex(b);
  1238.  1238   };
  1239.  1239  
  1240.  1240   return {
  1241.  1241   toHex : function(color) {
  1242.  1242   return color.replace(rgbRegExp, toHex);
  1243.  1243   },
  1244.  1244  
  1245.  1245   parse : function(css) {
  1246.  1246   var styles = {}, matches, name, value, isEncoded, urlConverter = settings.url_converter, urlConverterScope = settings.url_converter_scope || this;
  1247.  1247  
  1248.  1248   function compress(prefix, suffix) {
  1249.  1249   var top, right, bottom, left;
  1250.  1250  
  1251.  1251   // Get values and check it it needs compressing
  1252.  1252   top = styles[prefix + '-top' + suffix];
  1253.  1253   if (!top)
  1254.  1254   return;
  1255.  1255  
  1256.  1256   right = styles[prefix + '-right' + suffix];
  1257.  1257   if (top != right)
  1258.  1258   return;
  1259.  1259  
  1260.  1260   bottom = styles[prefix + '-bottom' + suffix];
  1261.  1261   if (right != bottom)
  1262.  1262   return;
  1263.  1263  
  1264.  1264   left = styles[prefix + '-left' + suffix];
  1265.  1265   if (bottom != left)
  1266.  1266   return;
  1267.  1267  
  1268.  1268   // Compress
  1269.  1269   styles[prefix + suffix] = left;
  1270.  1270   delete styles[prefix + '-top' + suffix];
  1271.  1271   delete styles[prefix + '-right' + suffix];
  1272.  1272   delete styles[prefix + '-bottom' + suffix];
  1273.  1273   delete styles[prefix + '-left' + suffix];
  1274.  1274   };
  1275.  1275  
  1276.  1276   function canCompress(key) {
  1277.  1277   var value = styles[key], i;
  1278.  1278  
  1279.  1279   if (!value || value.indexOf(' ') < 0)
  1280.  1280   return;
  1281.  1281  
  1282.  1282   value = value.split(' ');
  1283.  1283   i = value.length;
  1284.  1284   while (i--) {
  1285.  1285   if (value[i] !== value[0])
  1286.  1286   return false;
  1287.  1287   }
  1288.  1288  
  1289.  1289   styles[key] = value[0];
  1290.  1290  
  1291.  1291   return true;
  1292.  1292   };
  1293.  1293  
  1294.  1294   function compress2(target, a, b, c) {
  1295.  1295   if (!canCompress(a))
  1296.  1296   return;
  1297.  1297  
  1298.  1298   if (!canCompress(b))
  1299.  1299   return;
  1300.  1300  
  1301.  1301   if (!canCompress(c))
  1302.  1302   return;
  1303.  1303  
  1304.  1304   // Compress
  1305.  1305   styles[target] = styles[a] + ' ' + styles[b] + ' ' + styles[c];
  1306.  1306   delete styles[a];
  1307.  1307   delete styles[b];
  1308.  1308   delete styles[c];
  1309.  1309   };
  1310.  1310  
  1311.  1311   // Encodes the specified string by replacing all \" \' ; : with _<num>
  1312.  1312   function encode(str) {
  1313.  1313   isEncoded = true;
  1314.  1314  
  1315.  1315   return encodingLookup[str];
  1316.  1316   };
  1317.  1317  
  1318.  1318   // Decodes the specified string by replacing all _<num> with it's original value \" \' etc
  1319.  1319   // It will also decode the \" \' if keep_slashes is set to fale or omitted
  1320.  1320   function decode(str, keep_slashes) {
  1321.  1321   if (isEncoded) {
  1322.  1322   str = str.replace(/_[0-9]/g, function(str) {
  1323.  1323   return encodingLookup[str];
  1324.  1324   });
  1325.  1325   }
  1326.  1326  
  1327.  1327   if (!keep_slashes)
  1328.  1328   str = str.replace(/\\([\'\";:])/g, "$1");
  1329.  1329  
  1330.  1330   return str;
  1331.  1331   }
  1332.  1332  
  1333.  1333   if (css) {
  1334.  1334   // Encode \" \' % and ; and : inside strings so they don't interfere with the style parsing
  1335.  1335   css = css.replace(/\\[\"\';:_]/g, encode).replace(/\"[^\"]+\"|\'[^\']+\'/g, function(str) {
  1336.  1336   return str.replace(/[;:]/g, encode);
  1337.  1337   });
  1338.  1338  
  1339.  1339   // Parse styles
  1340.  1340   while (matches = styleRegExp.exec(css)) {
  1341.  1341   name = matches[1].replace(trimRightRegExp, '').toLowerCase();
  1342.  1342   value = matches[2].replace(trimRightRegExp, '');
  1343.  1343  
  1344.  1344   if (name && value.length > 0) {
  1345.  1345   // Opera will produce 700 instead of bold in their style values
  1346.  1346   if (name === 'font-weight' && value === '700')
  1347.  1347   value = 'bold';
  1348.  1348   else if (name === 'color' || name === 'background-color') // Lowercase colors like RED
  1349.  1349   value = value.toLowerCase();
  1350.  1350  
  1351.  1351   // Convert RGB colors to HEX
  1352.  1352   value = value.replace(rgbRegExp, toHex);
  1353.  1353  
  1354.  1354   // Convert URLs and force them into url('value') format
  1355.  1355   value = value.replace(urlOrStrRegExp, function(match, url, url2, url3, str, str2) {
  1356.  1356   str = str || str2;
  1357.  1357  
  1358.  1358   if (str) {
  1359.  1359   str = decode(str);
  1360.  1360  
  1361.  1361   // Force strings into single quote format
  1362.  1362   return "'" + str.replace(/\'/g, "\\'") + "'";
  1363.  1363   }
  1364.  1364  
  1365.  1365   url = decode(url || url2 || url3);
  1366.  1366  
  1367.  1367   // Convert the URL to relative/absolute depending on config
  1368.  1368   if (urlConverter)
  1369.  1369   url = urlConverter.call(urlConverterScope, url, 'style');
  1370.  1370  
  1371.  1371   // Output new URL format
  1372.  1372   return "url('" + url.replace(/\'/g, "\\'") + "')";
  1373.  1373   });
  1374.  1374  
  1375.  1375   styles[name] = isEncoded ? decode(value, true) : value;
  1376.  1376   }
  1377.  1377  
  1378.  1378   styleRegExp.lastIndex = matches.index + matches[0].length;
  1379.  1379   }
  1380.  1380  
  1381.  1381   // Compress the styles to reduce it's size for example IE will expand styles
  1382.  1382   compress("border", "");
  1383.  1383   compress("border", "-width");
  1384.  1384   compress("border", "-color");
  1385.  1385   compress("border", "-style");
  1386.  1386   compress("padding", "");
  1387.  1387   compress("margin", "");
  1388.  1388   compress2('border', 'border-width', 'border-style', 'border-color');
  1389.  1389  
  1390.  1390   // Remove pointless border, IE produces these
  1391.  1391   if (styles.border === 'medium none')
  1392.  1392   delete styles.border;
  1393.  1393   }
  1394.  1394  
  1395.  1395   return styles;
  1396.  1396   },
  1397.  1397  
  1398.  1398   serialize : function(styles, element_name) {
  1399.  1399   var css = '', name, value;
  1400.  1400  
  1401.  1401   function serializeStyles(name) {
  1402.  1402   var styleList, i, l, name, value;
  1403.  1403  
  1404.  1404   styleList = schema.styles[name];
  1405.  1405   if (styleList) {
  1406.  1406   for (i = 0, l = styleList.length; i < l; i++) {
  1407.  1407   name = styleList[i];
  1408.  1408   value = styles[name];
  1409.  1409  
  1410.  1410   if (value !== undef)
  1411.  1411   css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';';
  1412.  1412   }
  1413.  1413   }
  1414.  1414   };
  1415.  1415  
  1416.  1416   // Serialize styles according to schema
  1417.  1417   if (element_name && schema && schema.styles) {
  1418.  1418   // Serialize global styles and element specific styles
  1419.  1419   serializeStyles('*');
  1420.  1420   serializeStyles(name);
  1421.  1421   } else {
  1422.  1422   // Output the styles in the order they are inside the object
  1423.  1423   for (name in styles) {
  1424.  1424   value = styles[name];
  1425.  1425  
  1426.  1426   if (value !== undef)
  1427.  1427   css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';';
  1428.  1428   }
  1429.  1429   }
  1430.  1430  
  1431.  1431   return css;
  1432.  1432   }
  1433.  1433   };
  1434.  1434  };
  1435.  1435  
  1436.  1436  (function(tinymce) {
  1437.  1437   var transitional = {}, boolAttrMap, blockElementsMap, shortEndedElementsMap, nonEmptyElementsMap,
  1438.  1438   whiteSpaceElementsMap, selfClosingElementsMap, makeMap = tinymce.makeMap, each = tinymce.each;
  1439.  1439  
  1440.  1440   function split(str, delim) {
  1441.  1441   return str.split(delim || ',');
  1442.  1442   };
  1443.  1443  
  1444.  1444   function unpack(lookup, data) {
  1445.  1445   var key, elements = {};
  1446.  1446  
  1447.  1447   function replace(value) {
  1448.  1448   return value.replace(/[A-Z]+/g, function(key) {
  1449.  1449   return replace(lookup[key]);
  1450.  1450   });
  1451.  1451   };
  1452.  1452  
  1453.  1453   // Unpack lookup
  1454.  1454   for (key in lookup) {
  1455.  1455   if (lookup.hasOwnProperty(key))
  1456.  1456   lookup[key] = replace(lookup[key]);
  1457.  1457   }
  1458.  1458  
  1459.  1459   // Unpack and parse data into object map
  1460.  1460   replace(data).replace(/#/g, '#text').replace(/(\w+)\[([^\]]+)\]\[([^\]]*)\]/g, function(str, name, attributes, children) {
  1461.  1461   attributes = split(attributes, '|');
  1462.  1462  
  1463.  1463   elements[name] = {
  1464.  1464   attributes : makeMap(attributes),
  1465.  1465   attributesOrder : attributes,
  1466.  1466   children : makeMap(children, '|', {'#comment' : {}})
  1467.  1467   }
  1468.  1468   });
  1469.  1469  
  1470.  1470   return elements;
  1471.  1471   };
  1472.  1472  
  1473.  1473   // Build a lookup table for block elements both lowercase and uppercase
  1474.  1474   blockElementsMap = 'h1,h2,h3,h4,h5,h6,hr,p,div,address,pre,form,table,tbody,thead,tfoot,' +
  1475.  1475   'th,tr,td,li,ol,ul,caption,blockquote,center,dl,dt,dd,dir,fieldset,' +
  1476.  1476   'noscript,menu,isindex,samp,header,footer,article,section,hgroup';
  1477.  1477   blockElementsMap = makeMap(blockElementsMap, ',', makeMap(blockElementsMap.toUpperCase()));
  1478.  1478  
  1479.  1479   // This is the XHTML 1.0 transitional elements with it's attributes and children packed to reduce it's size
  1480.  1480   transitional = unpack({
  1481.  1481   Z : 'H|K|N|O|P',
  1482.  1482   Y : 'X|form|R|Q',
  1483.  1483   ZG : 'E|span|width|align|char|charoff|valign',
  1484.  1484   X : 'p|T|div|U|W|isindex|fieldset|table',
  1485.  1485   ZF : 'E|align|char|charoff|valign',
  1486.  1486   W : 'pre|hr|blockquote|address|center|noframes',
  1487.  1487   ZE : 'abbr|axis|headers|scope|rowspan|colspan|align|char|charoff|valign|nowrap|bgcolor|width|height',
  1488.  1488   ZD : '[E][S]',
  1489.  1489   U : 'ul|ol|dl|menu|dir',
  1490.  1490   ZC : 'p|Y|div|U|W|table|br|span|bdo|object|applet|img|map|K|N|Q',
  1491.  1491   T : 'h1|h2|h3|h4|h5|h6',
  1492.  1492   ZB : 'X|S|Q',
  1493.  1493   S : 'R|P',
  1494.  1494   ZA : 'a|G|J|M|O|P',
  1495.  1495   R : 'a|H|K|N|O',
  1496.  1496   Q : 'noscript|P',
  1497.  1497   P : 'ins|del|script',
  1498.  1498   O : 'input|select|textarea|label|button',
  1499.  1499   N : 'M|L',
  1500.  1500   M : 'em|strong|dfn|code|q|samp|kbd|var|cite|abbr|acronym',
  1501.  1501   L : 'sub|sup',
  1502.  1502   K : 'J|I',
  1503.  1503   J : 'tt|i|b|u|s|strike',
  1504.  1504   I : 'big|small|font|basefont',
  1505.  1505   H : 'G|F',
  1506.  1506   G : 'br|span|bdo',
  1507.  1507   F : 'object|applet|img|map|iframe',
  1508.  1508   E : 'A|B|C',
  1509.  1509   D : 'accesskey|tabindex|onfocus|onblur',
  1510.  1510   C : 'onclick|ondblclick|onmousedown|onmouseup|onmouseover|onmousemove|onmouseout|onkeypress|onkeydown|onkeyup',
  1511.  1511   B : 'lang|xml:lang|dir',
  1512.  1512   A : 'id|class|style|title'
  1513.  1513   }, 'script[id|charset|type|language|src|defer|xml:space][]' +
  1514.  1514   'style[B|id|type|media|title|xml:space][]' +
  1515.  1515   'object[E|declare|classid|codebase|data|type|codetype|archive|standby|width|height|usemap|name|tabindex|align|border|hspace|vspace][#|param|Y]' +
  1516.  1516   'param[id|name|value|valuetype|type][]' +
  1517.  1517   'p[E|align][#|S]' +
  1518.  1518   'a[E|D|charset|type|name|href|hreflang|rel|rev|shape|coords|target][#|Z]' +
  1519.  1519   'br[A|clear][]' +
  1520.  1520   'span[E][#|S]' +
  1521.  1521   'bdo[A|C|B][#|S]' +
  1522.  1522   'applet[A|codebase|archive|code|object|alt|name|width|height|align|hspace|vspace][#|param|Y]' +
  1523.  1523   'h1[E|align][#|S]' +
  1524.  1524   'img[E|src|alt|name|longdesc|width|height|usemap|ismap|align|border|hspace|vspace][]' +
  1525.  1525   'map[B|C|A|name][X|form|Q|area]' +
  1526.  1526   'h2[E|align][#|S]' +
  1527.  1527   'iframe[A|longdesc|name|src|frameborder|marginwidth|marginheight|scrolling|align|width|height][#|Y]' +
  1528.  1528   'h3[E|align][#|S]' +
  1529.  1529   'tt[E][#|S]' +
  1530.  1530   'i[E][#|S]' +
  1531.  1531   'b[E][#|S]' +
  1532.  1532   'u[E][#|S]' +
  1533.  1533   's[E][#|S]' +
  1534.  1534   'strike[E][#|S]' +
  1535.  1535   'big[E][#|S]' +
  1536.  1536   'small[E][#|S]' +
  1537.  1537   'font[A|B|size|color|face][#|S]' +
  1538.  1538   'basefont[id|size|color|face][]' +
  1539.  1539   'em[E][#|S]' +
  1540.  1540   'strong[E][#|S]' +
  1541.  1541   'dfn[E][#|S]' +
  1542.  1542   'code[E][#|S]' +
  1543.  1543   'q[E|cite][#|S]' +
  1544.  1544   'samp[E][#|S]' +
  1545.  1545   'kbd[E][#|S]' +
  1546.  1546   'var[E][#|S]' +
  1547.  1547   'cite[E][#|S]' +
  1548.  1548   'abbr[E][#|S]' +
  1549.  1549   'acronym[E][#|S]' +
  1550.  1550   'sub[E][#|S]' +
  1551.  1551   'sup[E][#|S]' +
  1552.  1552   'input[E|D|type|name|value|checked|disabled|readonly|size|maxlength|src|alt|usemap|onselect|onchange|accept|align][]' +
  1553.  1553   'select[E|name|size|multiple|disabled|tabindex|onfocus|onblur|onchange][optgroup|option]' +
  1554.  1554   'optgroup[E|disabled|label][option]' +
  1555.  1555   'option[E|selected|disabled|label|value][]' +
  1556.  1556   'textarea[E|D|name|rows|cols|disabled|readonly|onselect|onchange][]' +
  1557.  1557   'label[E|for|accesskey|onfocus|onblur][#|S]' +
  1558.  1558   'button[E|D|name|value|type|disabled][#|p|T|div|U|W|table|G|object|applet|img|map|K|N|Q]' +
  1559.  1559   'h4[E|align][#|S]' +
  1560.  1560   'ins[E|cite|datetime][#|Y]' +
  1561.  1561   'h5[E|align][#|S]' +
  1562.  1562   'del[E|cite|datetime][#|Y]' +
  1563.  1563   'h6[E|align][#|S]' +
  1564.  1564   'div[E|align][#|Y]' +
  1565.  1565   'ul[E|type|compact][li]' +
  1566.  1566   'li[E|type|value][#|Y]' +
  1567.  1567   'ol[E|type|compact|start][li]' +
  1568.  1568   'dl[E|compact][dt|dd]' +
  1569.  1569   'dt[E][#|S]' +
  1570.  1570   'dd[E][#|Y]' +
  1571.  1571   'menu[E|compact][li]' +
  1572.  1572   'dir[E|compact][li]' +
  1573.  1573   'pre[E|width|xml:space][#|ZA]' +
  1574.  1574   'hr[E|align|noshade|size|width][]' +
  1575.  1575   'blockquote[E|cite][#|Y]' +
  1576.  1576   'address[E][#|S|p]' +
  1577.  1577   'center[E][#|Y]' +
  1578.  1578   'noframes[E][#|Y]' +
  1579.  1579   'isindex[A|B|prompt][]' +
  1580.  1580   'fieldset[E][#|legend|Y]' +
  1581.  1581   'legend[E|accesskey|align][#|S]' +
  1582.  1582   'table[E|summary|width|border|frame|rules|cellspacing|cellpadding|align|bgcolor][caption|col|colgroup|thead|tfoot|tbody|tr]' +
  1583.  1583   'caption[E|align][#|S]' +
  1584.  1584   'col[ZG][]' +
  1585.  1585   'colgroup[ZG][col]' +
  1586.  1586   'thead[ZF][tr]' +
  1587.  1587   'tr[ZF|bgcolor][th|td]' +
  1588.  1588   'th[E|ZE][#|Y]' +
  1589.  1589   'form[E|action|method|name|enctype|onsubmit|onreset|accept|accept-charset|target][#|X|R|Q]' +
  1590.  1590   'noscript[E][#|Y]' +
  1591.  1591   'td[E|ZE][#|Y]' +
  1592.  1592   'tfoot[ZF][tr]' +
  1593.  1593   'tbody[ZF][tr]' +
  1594.  1594   'area[E|D|shape|coords|href|nohref|alt|target][]' +
  1595.  1595   'base[id|href|target][]' +
  1596.  1596   'body[E|onload|onunload|background|bgcolor|text|link|vlink|alink][#|Y]'
  1597.  1597   );
  1598.  1598  
  1599.  1599   boolAttrMap = makeMap('checked,compact,declare,defer,disabled,ismap,multiple,nohref,noresize,noshade,nowrap,readonly,selected,preload,autoplay,loop,controls');
  1600.  1600   shortEndedElementsMap = makeMap('area,base,basefont,br,col,frame,hr,img,input,isindex,link,meta,param,embed,source');
  1601.  1601   nonEmptyElementsMap = tinymce.extend(makeMap('td,th,iframe,video,object'), shortEndedElementsMap);
  1602.  1602   whiteSpaceElementsMap = makeMap('pre,script,style');
  1603.  1603   selfClosingElementsMap = makeMap('colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr');
  1604.  1604  
  1605.  1605   tinymce.html.Schema = function(settings) {
  1606.  1606   var self = this, elements = {}, children = {}, patternElements = [], validStyles;
  1607.  1607  
  1608.  1608   settings = settings || {};
  1609.  1609  
  1610.  1610   // Allow all elements and attributes if verify_html is set to false
  1611.  1611   if (settings.verify_html === false)
  1612.  1612   settings.valid_elements = '*[*]';
  1613.  1613  
  1614.  1614   // Build styles list
  1615.  1615   if (settings.valid_styles) {
  1616.  1616   validStyles = {};
  1617.  1617  
  1618.  1618   // Convert styles into a rule list
  1619.  1619   each(settings.valid_styles, function(value, key) {
  1620.  1620   validStyles[key] = tinymce.explode(value);
  1621.  1621   });
  1622.  1622   }
  1623.  1623  
  1624.  1624   // Converts a wildcard expression string to a regexp for example *a will become /.*a/.
  1625.  1625   function patternToRegExp(str) {
  1626.  1626   return new RegExp('^' + str.replace(/([?+*])/g, '.$1') + '$');
  1627.  1627   };
  1628.  1628  
  1629.  1629   // Parses the specified valid_elements string and adds to the current rules
  1630.  1630   // This function is a bit hard to read since it's heavily optimized for speed
  1631.  1631   function addValidElements(valid_elements) {
  1632.  1632   var ei, el, ai, al, yl, matches, element, attr, attrData, elementName, attrName, attrType, attributes, attributesOrder,
  1633.  1633   prefix, outputName, globalAttributes, globalAttributesOrder, transElement, key, childKey, value,
  1634.  1634   elementRuleRegExp = /^([#+-])?([^\[\/]+)(?:\/([^\[]+))?(?:\[([^\]]+)\])?$/,
  1635.  1635   attrRuleRegExp = /^([!\-])?(\w+::\w+|[^=:<]+)?(?:([=:<])(.*))?$/,
  1636.  1636   hasPatternsRegExp = /[*?+]/;
  1637.  1637  
  1638.  1638   if (valid_elements) {
  1639.  1639   // Split valid elements into an array with rules
  1640.  1640   valid_elements = split(valid_elements);
  1641.  1641  
  1642.  1642   if (elements['@']) {
  1643.  1643   globalAttributes = elements['@'].attributes;
  1644.  1644   globalAttributesOrder = elements['@'].attributesOrder;
  1645.  1645   }
  1646.  1646  
  1647.  1647   // Loop all rules
  1648.  1648   for (ei = 0, el = valid_elements.length; ei < el; ei++) {
  1649.  1649   // Parse element rule
  1650.  1650   matches = elementRuleRegExp.exec(valid_elements[ei]);
  1651.  1651   if (matches) {
  1652.  1652   // Setup local names for matches
  1653.  1653   prefix = matches[1];
  1654.  1654   elementName = matches[2];
  1655.  1655   outputName = matches[3];
  1656.  1656   attrData = matches[4];
  1657.  1657  
  1658.  1658   // Create new attributes and attributesOrder
  1659.  1659   attributes = {};
  1660.  1660   attributesOrder = [];
  1661.  1661  
  1662.  1662   // Create the new element
  1663.  1663   element = {
  1664.  1664   attributes : attributes,
  1665.  1665   attributesOrder : attributesOrder
  1666.  1666   };
  1667.  1667  
  1668.  1668   // Padd empty elements prefix
  1669.  1669   if (prefix === '#')
  1670.  1670   element.paddEmpty = true;
  1671.  1671  
  1672.  1672   // Remove empty elements prefix
  1673.  1673   if (prefix === '-')
  1674.  1674   element.removeEmpty = true;
  1675.  1675  
  1676.  1676   // Copy attributes from global rule into current rule
  1677.  1677   if (globalAttributes) {
  1678.  1678   for (key in globalAttributes)
  1679.  1679   attributes[key] = globalAttributes[key];
  1680.  1680  
  1681.  1681   attributesOrder.push.apply(attributesOrder, globalAttributesOrder);
  1682.  1682   }
  1683.  1683  
  1684.  1684   // Attributes defined
  1685.  1685   if (attrData) {
  1686.  1686   attrData = split(attrData, '|');
  1687.  1687   for (ai = 0, al = attrData.length; ai < al; ai++) {
  1688.  1688   matches = attrRuleRegExp.exec(attrData[ai]);
  1689.  1689   if (matches) {
  1690.  1690   attr = {};
  1691.  1691   attrType = matches[1];
  1692.  1692   attrName = matches[2].replace(/::/g, ':');
  1693.  1693   prefix = matches[3];
  1694.  1694   value = matches[4];
  1695.  1695  
  1696.  1696   // Required
  1697.  1697   if (attrType === '!') {
  1698.  1698   element.attributesRequired = element.attributesRequired || [];
  1699.  1699   element.attributesRequired.push(attrName);
  1700.  1700   attr.required = true;
  1701.  1701   }
  1702.  1702  
  1703.  1703   // Denied from global
  1704.  1704   if (attrType === '-') {
  1705.  1705   delete attributes[attrName];
  1706.  1706   attributesOrder.splice(tinymce.inArray(attributesOrder, attrName), 1);
  1707.  1707   continue;
  1708.  1708   }
  1709.  1709  
  1710.  1710   // Default value
  1711.  1711   if (prefix) {
  1712.  1712   // Default value
  1713.  1713   if (prefix === '=') {
  1714.  1714   element.attributesDefault = element.attributesDefault || [];
  1715.  1715   element.attributesDefault.push({name: attrName, value: value});
  1716.  1716   attr.defaultValue = value;
  1717.  1717   }
  1718.  1718  
  1719.  1719   // Forced value
  1720.  1720   if (prefix === ':') {
  1721.  1721   element.attributesForced = element.attributesForced || [];
  1722.  1722   element.attributesForced.push({name: attrName, value: value});
  1723.  1723   attr.forcedValue = value;
  1724.  1724   }
  1725.  1725  
  1726.  1726   // Required values
  1727.  1727   if (prefix === '<')
  1728.  1728   attr.validValues = makeMap(value, '?');
  1729.  1729   }
  1730.  1730  
  1731.  1731   // Check for attribute patterns
  1732.  1732   if (hasPatternsRegExp.test(attrName)) {
  1733.  1733   element.attributePatterns = element.attributePatterns || [];
  1734.  1734   attr.pattern = patternToRegExp(attrName);
  1735.  1735   element.attributePatterns.push(attr);
  1736.  1736   } else {
  1737.  1737   // Add attribute to order list if it doesn't already exist
  1738.  1738   if (!attributes[attrName])
  1739.  1739   attributesOrder.push(attrName);
  1740.  1740  
  1741.  1741   attributes[attrName] = attr;
  1742.  1742   }
  1743.  1743   }
  1744.  1744   }
  1745.  1745   }
  1746.  1746  
  1747.  1747   // Global rule, store away these for later usage
  1748.  1748   if (!globalAttributes && elementName == '@') {
  1749.  1749   globalAttributes = attributes;
  1750.  1750   globalAttributesOrder = attributesOrder;
  1751.  1751   }
  1752.  1752  
  1753.  1753   // Handle substitute elements such as b/strong
  1754.  1754   if (outputName) {
  1755.  1755   element.outputName = elementName;
  1756.  1756   elements[outputName] = element;
  1757.  1757   }
  1758.  1758  
  1759.  1759   // Add pattern or exact element
  1760.  1760   if (hasPatternsRegExp.test(elementName)) {
  1761.  1761   element.pattern = patternToRegExp(elementName);
  1762.  1762   patternElements.push(element);
  1763.  1763   } else
  1764.  1764   elements[elementName] = element;
  1765.  1765   }
  1766.  1766   }
  1767.  1767   }
  1768.  1768   };
  1769.  1769  
  1770.  1770   function setValidElements(valid_elements) {
  1771.  1771   elements = {};
  1772.  1772   patternElements = [];
  1773.  1773  
  1774.  1774   addValidElements(valid_elements);
  1775.  1775  
  1776.  1776   each(transitional, function(element, name) {
  1777.  1777   children[name] = element.children;
  1778.  1778   });
  1779.  1779   };
  1780.  1780  
  1781.  1781   // Adds custom non HTML elements to the schema
  1782.  1782   function addCustomElements(custom_elements) {
  1783.  1783   var customElementRegExp = /^(~)?(.+)$/;
  1784.  1784  
  1785.  1785   if (custom_elements) {
  1786.  1786   each(split(custom_elements), function(rule) {
  1787.  1787   var matches = customElementRegExp.exec(rule),
  1788.  1788   cloneName = matches[1] === '~' ? 'span' : 'div',
  1789.  1789   name = matches[2];
  1790.  1790  
  1791.  1791   children[name] = children[cloneName];
  1792.  1792  
  1793.  1793   // Add custom elements at span/div positions
  1794.  1794   each(children, function(element, child) {
  1795.  1795   if (element[cloneName])
  1796.  1796   element[name] = element[cloneName];
  1797.  1797   });
  1798.  1798   });
  1799.  1799   }
  1800.  1800   };
  1801.  1801  
  1802.  1802   // Adds valid children to the schema object
  1803.  1803   function addValidChildren(valid_children) {
  1804.  1804   var childRuleRegExp = /^([+\-]?)(\w+)\[([^\]]+)\]$/;
  1805.  1805  
  1806.  1806   if (valid_children) {
  1807.  1807   each(split(valid_children), function(rule) {
  1808.  1808   var matches = childRuleRegExp.exec(rule), parent, prefix;
  1809.  1809  
  1810.  1810   if (matches) {
  1811.  1811   prefix = matches[1];
  1812.  1812  
  1813.  1813   // Add/remove items from default
  1814.  1814   if (prefix)
  1815.  1815   parent = children[matches[2]];
  1816.  1816   else
  1817.  1817   parent = children[matches[2]] = {'#comment' : {}};
  1818.  1818  
  1819.  1819   parent = children[matches[2]];
  1820.  1820  
  1821.  1821   each(split(matches[3], '|'), function(child) {
  1822.  1822   if (prefix === '-')
  1823.  1823   delete parent[child];
  1824.  1824   else
  1825.  1825   parent[child] = {};
  1826.  1826   });
  1827.  1827   }
  1828.  1828   });
  1829.  1829   }
  1830.  1830   }
  1831.  1831  
  1832.  1832   if (!settings.valid_elements) {
  1833.  1833   // No valid elements defined then clone the elements from the transitional spec
  1834.  1834   each(transitional, function(element, name) {
  1835.  1835   elements[name] = {
  1836.  1836   attributes : element.attributes,
  1837.  1837   attributesOrder : element.attributesOrder
  1838.  1838   };
  1839.  1839  
  1840.  1840   children[name] = element.children;
  1841.  1841   });
  1842.  1842  
  1843.  1843   // Switch these
  1844.  1844   each(split('strong/b,em/i'), function(item) {
  1845.  1845   item = split(item, '/');
  1846.  1846   elements[item[1]].outputName = item[0];
  1847.  1847   });
  1848.  1848  
  1849.  1849   // Add default alt attribute for images
  1850.  1850   elements.img.attributesDefault = [{name: 'alt', value: ''}];
  1851.  1851  
  1852.  1852   // Remove these if they are empty by default
  1853.  1853   each(split('ol,ul,li,sub,sup,blockquote,tr,div,span,font,a,table,tbody'), function(name) {
  1854.  1854   elements[name].removeEmpty = true;
  1855.  1855   });
  1856.  1856  
  1857.  1857   // Padd these by default
  1858.  1858   each(split('p,h1,h2,h3,h4,h5,h6,th,td,pre,div,address,caption'), function(name) {
  1859.  1859   elements[name].paddEmpty = true;
  1860.  1860   });
  1861.  1861   } else
  1862.  1862   setValidElements(settings.valid_elements);
  1863.  1863  
  1864.  1864   addCustomElements(settings.custom_elements);
  1865.  1865   addValidChildren(settings.valid_children);
  1866.  1866   addValidElements(settings.extended_valid_elements);
  1867.  1867  
  1868.  1868   // Todo: Remove this when we fix list handling to be valid
  1869.  1869   addValidChildren('+ol[ul|ol],+ul[ul|ol]');
  1870.  1870  
  1871.  1871   // Delete invalid elements
  1872.  1872   if (settings.invalid_elements) {
  1873.  1873   tinymce.each(tinymce.explode(settings.invalid_elements), function(item) {
  1874.  1874   if (elements[item])
  1875.  1875   delete elements[item];
  1876.  1876   });
  1877.  1877   }
  1878.  1878  
  1879.  1879   self.children = children;
  1880.  1880  
  1881.  1881   self.styles = validStyles;
  1882.  1882  
  1883.  1883   self.getBoolAttrs = function() {
  1884.  1884   return boolAttrMap;
  1885.  1885   };
  1886.  1886  
  1887.  1887   self.getBlockElements = function() {
  1888.  1888   return blockElementsMap;
  1889.  1889   };
  1890.  1890  
  1891.  1891   self.getShortEndedElements = function() {
  1892.  1892   return shortEndedElementsMap;
  1893.  1893   };
  1894.  1894  
  1895.  1895   self.getSelfClosingElements = function() {
  1896.  1896   return selfClosingElementsMap;
  1897.  1897   };
  1898.  1898  
  1899.  1899   self.getNonEmptyElements = function() {
  1900.  1900   return nonEmptyElementsMap;
  1901.  1901   };
  1902.  1902  
  1903.  1903   self.getWhiteSpaceElements = function() {
  1904.  1904   return whiteSpaceElementsMap;
  1905.  1905   };
  1906.  1906  
  1907.  1907   self.isValidChild = function(name, child) {
  1908.  1908   var parent = children[name];
  1909.  1909  
  1910.  1910   return !!(parent && parent[child]);
  1911.  1911   };
  1912.  1912  
  1913.  1913   self.getElementRule = function(name) {
  1914.  1914   var element = elements[name], i;
  1915.  1915  
  1916.  1916   // Exact match found
  1917.  1917   if (element)
  1918.  1918   return element;
  1919.  1919  
  1920.  1920   // No exact match then try the patterns
  1921.  1921   i = patternElements.length;
  1922.  1922   while (i--) {
  1923.  1923   element = patternElements[i];
  1924.  1924  
  1925.  1925   if (element.pattern.test(name))
  1926.  1926   return element;
  1927.  1927   }
  1928.  1928   };
  1929.  1929  
  1930.  1930   self.addValidElements = addValidElements;
  1931.  1931  
  1932.  1932   self.setValidElements = setValidElements;
  1933.  1933  
  1934.  1934   self.addCustomElements = addCustomElements;
  1935.  1935  
  1936.  1936   self.addValidChildren = addValidChildren;
  1937.  1937   };
  1938.  1938  
  1939.  1939   // Expose boolMap and blockElementMap as static properties for usage in DOMUtils
  1940.  1940   tinymce.html.Schema.boolAttrMap = boolAttrMap;
  1941.  1941   tinymce.html.Schema.blockElementsMap = blockElementsMap;
  1942.  1942  })(tinymce);
  1943.  1943  
  1944.  1944  (function(tinymce) {
  1945.  1945   tinymce.html.SaxParser = function(settings, schema) {
  1946.  1946   var self = this, noop = function() {};
  1947.  1947  
  1948.  1948   settings = settings || {};
  1949.  1949   self.schema = schema = schema || new tinymce.html.Schema();
  1950.  1950  
  1951.  1951   if (settings.fix_self_closing !== false)
  1952.  1952   settings.fix_self_closing = true;
  1953.  1953  
  1954.  1954   // Add handler functions from settings and setup default handlers
  1955.  1955   tinymce.each('comment cdata text start end pi doctype'.split(' '), function(name) {
  1956.  1956   if (name)
  1957.  1957   self[name] = settings[name] || noop;
  1958.  1958   });
  1959.  1959  
  1960.  1960   self.parse = function(html) {
  1961.  1961   var self = this, matches, index = 0, value, endRegExp, stack = [], attrList, i, text, name,
  1962.  1962   shortEndedElements, fillAttrsMap, isShortEnded, validate, elementRule, isValidElement, attr, attribsValue,
  1963.  1963   validAttributesMap, validAttributePatterns, attributesRequired, attributesDefault, attributesForced, selfClosing,
  1964.  1964   tokenRegExp, attrRegExp, specialElements, attrValue, idCount = 0, decode = tinymce.html.Entities.decode, fixSelfClosing;
  1965.  1965  
  1966.  1966   function processEndTag(name) {
  1967.  1967   var pos, i;
  1968.  1968  
  1969.  1969   // Find position of parent of the same type
  1970.  1970   pos = stack.length;
  1971.  1971   while (pos--) {
  1972.  1972   if (stack[pos].name === name)
  1973.  1973   break;
  1974.  1974   }
  1975.  1975  
  1976.  1976   // Found parent
  1977.  1977   if (pos >= 0) {
  1978.  1978   // Close all the open elements
  1979.  1979   for (i = stack.length - 1; i >= pos; i--) {
  1980.  1980   name = stack[i];
  1981.  1981  
  1982.  1982   if (name.valid)
  1983.  1983   self.end(name.name);
  1984.  1984   }
  1985.  1985  
  1986.  1986   // Remove the open elements from the stack
  1987.  1987   stack.length = pos;
  1988.  1988   }
  1989.  1989   };
  1990.  1990  
  1991.  1991   // Precompile RegExps and map objects
  1992.  1992   tokenRegExp = new RegExp('<(?:' +
  1993.  1993   '(?:!--([\\w\\W]*?)-->)|' + // Comment
  1994.  1994   '(?:!\\[CDATA\\[([\\w\\W]*?)\\]\\]>)|' + // CDATA
  1995.  1995   '(?:!DOCTYPE([\\w\\W]*?)>)|' + // DOCTYPE
  1996.  1996   '(?:\\?([^\\s\\/<>]+) ?([\\w\\W]*?)[?/]>)|' + // PI
  1997.  1997   '(?:\\/([^>]+)>)|' + // End element
  1998.  1998   '(?:([^\\s\\/<>]+)\\s*((?:[^"\'>]+(?:(?:"[^"]*")|(?:\'[^\']*\')|[^>]*))*)>)' + // Start element
  1999.  1999   ')', 'g');
  2000.  2000  
  2001.  2001   attrRegExp = /([\w:\-]+)(?:\s*=\s*(?:(?:\"((?:\\.|[^\"])*)\")|(?:\'((?:\\.|[^\'])*)\')|([^>\s]+)))?/g;
  2002.  2002   specialElements = {
  2003.  2003   'script' : /<\/script[^>]*>/gi,
  2004.  2004   'style' : /<\/style[^>]*>/gi,
  2005.  2005   'noscript' : /<\/noscript[^>]*>/gi
  2006.  2006   };
  2007.  2007  
  2008.  2008   // Setup lookup tables for empty elements and boolean attributes
  2009.  2009   shortEndedElements = schema.getShortEndedElements();
  2010.  2010   selfClosing = schema.getSelfClosingElements();
  2011.  2011   fillAttrsMap = schema.getBoolAttrs();
  2012.  2012   validate = settings.validate;
  2013.  2013   fixSelfClosing = settings.fix_self_closing;
  2014.  2014  
  2015.  2015   while (matches = tokenRegExp.exec(html)) {
  2016.  2016   // Text
  2017.  2017   if (index < matches.index)
  2018.  2018   self.text(decode(html.substr(index, matches.index - index)));
  2019.  2019  
  2020.  2020   if (value = matches[6]) { // End element
  2021.  2021   processEndTag(value.toLowerCase());
  2022.  2022   } else if (value = matches[7]) { // Start element
  2023.  2023   value = value.toLowerCase();
  2024.  2024   isShortEnded = value in shortEndedElements;
  2025.  2025  
  2026.  2026   // Is self closing tag for example an <li> after an open <li>
  2027.  2027   if (fixSelfClosing && selfClosing[value] && stack.length > 0 && stack[stack.length - 1].name === value)
  2028.  2028   processEndTag(value);
  2029.  2029  
  2030.  2030   // Validate element
  2031.  2031   if (!validate || (elementRule = schema.getElementRule(value))) {
  2032.  2032   isValidElement = true;
  2033.  2033  
  2034.  2034   // Grab attributes map and patters when validation is enabled
  2035.  2035   if (validate) {
  2036.  2036   validAttributesMap = elementRule.attributes;
  2037.  2037   validAttributePatterns = elementRule.attributePatterns;
  2038.  2038   }
  2039.  2039  
  2040.  2040   // Parse attributes
  2041.  2041   if (attribsValue = matches[8]) {
  2042.  2042   attrList = [];
  2043.  2043   attrList.map = {};
  2044.  2044  
  2045.  2045   attribsValue.replace(attrRegExp, function(match, name, value, val2, val3) {
  2046.  2046   var attrRule, i;
  2047.  2047  
  2048.  2048   name = name.toLowerCase();
  2049.  2049   value = name in fillAttrsMap ? name : decode(value || val2 || val3 || ''); // Handle boolean attribute than value attribute
  2050.  2050  
  2051.  2051   // Validate name and value
  2052.  2052   if (validate && name.indexOf('data-') !== 0) {
  2053.  2053   attrRule = validAttributesMap[name];
  2054.  2054  
  2055.  2055   // Find rule by pattern matching
  2056.  2056   if (!attrRule && validAttributePatterns) {
  2057.  2057   i = validAttributePatterns.length;
  2058.  2058   while (i--) {
  2059.  2059   attrRule = validAttributePatterns[i];
  2060.  2060   if (attrRule.pattern.test(name))
  2061.  2061   break;
  2062.  2062   }
  2063.  2063  
  2064.  2064   // No rule matched
  2065.  2065   if (i === -1)
  2066.  2066   attrRule = null;
  2067.  2067   }
  2068.  2068  
  2069.  2069   // No attribute rule found
  2070.  2070   if (!attrRule)
  2071.  2071   return;
  2072.  2072  
  2073.  2073   // Validate value
  2074.  2074   if (attrRule.validValues && !(value in attrRule.validValues))
  2075.  2075   return;
  2076.  2076   }
  2077.  2077  
  2078.  2078   // Add attribute to list and map
  2079.  2079   attrList.map[name] = value;
  2080.  2080   attrList.push({
  2081.  2081   name: name,
  2082.  2082   value: value
  2083.  2083   });
  2084.  2084   });
  2085.  2085   } else {
  2086.  2086   attrList = [];
  2087.  2087   attrList.map = {};
  2088.  2088   }
  2089.  2089  
  2090.  2090   // Process attributes if validation is enabled
  2091.  2091   if (validate) {
  2092.  2092   attributesRequired = elementRule.attributesRequired;
  2093.  2093   attributesDefault = elementRule.attributesDefault;
  2094.  2094   attributesForced = elementRule.attributesForced;
  2095.  2095  
  2096.  2096   // Handle forced attributes
  2097.  2097   if (attributesForced) {
  2098.  2098   i = attributesForced.length;
  2099.  2099   while (i--) {
  2100.  2100   attr = attributesForced[i];
  2101.  2101   name = attr.name;
  2102.  2102   attrValue = attr.value;
  2103.  2103  
  2104.  2104   if (attrValue === '{$uid}')
  2105.  2105   attrValue = 'mce_' + idCount++;
  2106.  2106  
  2107.  2107   attrList.map[name] = attrValue;
  2108.  2108   attrList.push({name: name, value: attrValue});
  2109.  2109   }
  2110.  2110   }
  2111.  2111  
  2112.  2112   // Handle default attributes
  2113.  2113   if (attributesDefault) {
  2114.  2114   i = attributesDefault.length;
  2115.  2115   while (i--) {
  2116.  2116   attr = attributesDefault[i];
  2117.  2117   name = attr.name;
  2118.  2118  
  2119.  2119   if (!(name in attrList.map)) {
  2120.  2120   attrValue = attr.value;
  2121.  2121  
  2122.  2122   if (attrValue === '{$uid}')
  2123.  2123   attrValue = 'mce_' + idCount++;
  2124.  2124  
  2125.  2125   attrList.map[name] = attrValue;
  2126.  2126   attrList.push({name: name, value: attrValue});
  2127.  2127   }
  2128.  2128   }
  2129.  2129   }
  2130.  2130  
  2131.  2131   // Handle required attributes
  2132.  2132   if (attributesRequired) {
  2133.  2133   i = attributesRequired.length;
  2134.  2134   while (i--) {
  2135.  2135   if (attributesRequired[i] in attrList.map)
  2136.  2136   break;
  2137.  2137   }
  2138.  2138  
  2139.  2139   // None of the required attributes where found
  2140.  2140   if (i === -1)
  2141.  2141   isValidElement = false;
  2142.  2142   }
  2143.  2143  
  2144.  2144   // Invalidate element if it's marked as bogus
  2145.  2145   if (attrList.map['data-mce-bogus'])
  2146.  2146   isValidElement = false;
  2147.  2147   }
  2148.  2148  
  2149.  2149   if (isValidElement)
  2150.  2150   self.start(value, attrList, isShortEnded);
  2151.  2151   } else
  2152.  2152   isValidElement = false;
  2153.  2153  
  2154.  2154   // Treat script, noscript and style a bit different since they may include code that looks like elements
  2155.  2155   if (endRegExp = specialElements[value]) {
  2156.  2156   endRegExp.lastIndex = index = matches.index + matches[0].length;
  2157.  2157  
  2158.  2158   if (matches = endRegExp.exec(html)) {
  2159.  2159   if (isValidElement)
  2160.  2160   text = html.substr(index, matches.index - index);
  2161.  2161  
  2162.  2162   index = matches.index + matches[0].length;
  2163.  2163   } else {
  2164.  2164   text = html.substr(index);
  2165.  2165   index = html.length;
  2166.  2166   }
  2167.  2167  
  2168.  2168   if (isValidElement && text.length > 0)
  2169.  2169   self.text(text, true);
  2170.  2170  
  2171.  2171   if (isValidElement)
  2172.  2172   self.end(value);
  2173.  2173  
  2174.  2174   tokenRegExp.lastIndex = index;
  2175.  2175   continue;
  2176.  2176   }
  2177.  2177  
  2178.  2178   // Push value on to stack
  2179.  2179   if (!isShortEnded) {
  2180.  2180   if (!attribsValue || attribsValue.indexOf('/') != attribsValue.length - 1)
  2181.  2181   stack.push({name: value, valid: isValidElement});
  2182.  2182   else if (isValidElement)
  2183.  2183   self.end(value);
  2184.  2184   }
  2185.  2185   } else if (value = matches[1]) { // Comment
  2186.  2186   self.comment(value);
  2187.  2187   } else if (value = matches[2]) { // CDATA
  2188.  2188   self.cdata(value);
  2189.  2189   } else if (value = matches[3]) { // DOCTYPE
  2190.  2190   self.doctype(value);
  2191.  2191   } else if (value = matches[4]) { // PI
  2192.  2192   self.pi(value, matches[5]);
  2193.  2193   }
  2194.  2194  
  2195.  2195   index = matches.index + matches[0].length;
  2196.  2196   }
  2197.  2197  
  2198.  2198   // Text
  2199.  2199   if (index < html.length)
  2200.  2200   self.text(decode(html.substr(index)));
  2201.  2201  
  2202.  2202   // Close any open elements
  2203.  2203   for (i = stack.length - 1; i >= 0; i--) {
  2204.  2204   value = stack[i];
  2205.  2205  
  2206.  2206   if (value.valid)
  2207.  2207   self.end(value.name);
  2208.  2208   }
  2209.  2209   };
  2210.  2210   }
  2211.  2211  })(tinymce);
  2212.  2212  
  2213.  2213  (function(tinymce) {
  2214.  2214   var whiteSpaceRegExp = /^[ \t\r\n]*$/, typeLookup = {
  2215.  2215   '#text' : 3,
  2216.  2216   '#comment' : 8,
  2217.  2217   '#cdata' : 4,
  2218.  2218   '#pi' : 7,
  2219.  2219   '#doctype' : 10,
  2220.  2220   '#document-fragment' : 11
  2221.  2221   };
  2222.  2222  
  2223.  2223   // Walks the tree left/right
  2224.  2224   function walk(node, root_node, prev) {
  2225.  2225   var sibling, parent, startName = prev ? 'lastChild' : 'firstChild', siblingName = prev ? 'prev' : 'next';
  2226.  2226  
  2227.  2227   // Walk into nodes if it has a start
  2228.  2228   if (node[startName])
  2229.  2229   return node[startName];
  2230.  2230  
  2231.  2231   // Return the sibling if it has one
  2232.  2232   if (node !== root_node) {
  2233.  2233   sibling = node[siblingName];
  2234.  2234  
  2235.  2235   if (sibling)
  2236.  2236   return sibling;
  2237.  2237  
  2238.  2238   // Walk up the parents to look for siblings
  2239.  2239   for (parent = node.parent; parent && parent !== root_node; parent = parent.parent) {
  2240.  2240   sibling = parent[siblingName];
  2241.  2241  
  2242.  2242   if (sibling)
  2243.  2243   return sibling;
  2244.  2244   }
  2245.  2245   }
  2246.  2246   };
  2247.  2247  
  2248.  2248   function Node(name, type) {
  2249.  2249   this.name = name;
  2250.  2250   this.type = type;
  2251.  2251  
  2252.  2252   if (type === 1) {
  2253.  2253   this.attributes = [];
  2254.  2254   this.attributes.map = {};
  2255.  2255   }
  2256.  2256   }
  2257.  2257  
  2258.  2258   tinymce.extend(Node.prototype, {
  2259.  2259   replace : function(node) {
  2260.  2260   var self = this;
  2261.  2261  
  2262.  2262   if (node.parent)
  2263.  2263   node.remove();
  2264.  2264  
  2265.  2265   self.insert(node, self);
  2266.  2266   self.remove();
  2267.  2267  
  2268.  2268   return self;
  2269.  2269   },
  2270.  2270  
  2271.  2271   attr : function(name, value) {
  2272.  2272   var self = this, attrs, i, undef;
  2273.  2273  
  2274.  2274   if (typeof name !== "string") {
  2275.  2275   for (i in name)
  2276.  2276   self.attr(i, name[i]);
  2277.  2277  
  2278.  2278   return self;
  2279.  2279   }
  2280.  2280  
  2281.  2281   if (attrs = self.attributes) {
  2282.  2282   if (value !== undef) {
  2283.  2283   // Remove attribute
  2284.  2284   if (value === null) {
  2285.  2285   if (name in attrs.map) {
  2286.  2286   delete attrs.map[name];
  2287.  2287  
  2288.  2288   i = attrs.length;
  2289.  2289   while (i--) {
  2290.  2290   if (attrs[i].name === name) {
  2291.  2291   attrs = attrs.splice(i, 1);
  2292.  2292   return self;
  2293.  2293   }
  2294.  2294   }
  2295.  2295   }
  2296.  2296  
  2297.  2297   return self;
  2298.  2298   }
  2299.  2299  
  2300.  2300   // Set attribute
  2301.  2301   if (name in attrs.map) {
  2302.  2302   // Set attribute
  2303.  2303   i = attrs.length;
  2304.  2304   while (i--) {
  2305.  2305   if (attrs[i].name === name) {
  2306.  2306   attrs[i].value = value;
  2307.  2307   break;
  2308.  2308   }
  2309.  2309   }
  2310.  2310   } else
  2311.  2311   attrs.push({name: name, value: value});
  2312.  2312  
  2313.  2313   attrs.map[name] = value;
  2314.  2314  
  2315.  2315   return self;
  2316.  2316   } else {
  2317.  2317   return attrs.map[name];
  2318.  2318   }
  2319.  2319   }
  2320.  2320   },
  2321.  2321  
  2322.  2322   clone : function() {
  2323.  2323   var self = this, clone = new Node(self.name, self.type), i, l, selfAttrs, selfAttr, cloneAttrs;
  2324.  2324  
  2325.  2325   // Clone element attributes
  2326.  2326   if (selfAttrs = self.attributes) {
  2327.  2327   cloneAttrs = [];
  2328.  2328   cloneAttrs.map = {};
  2329.  2329  
  2330.  2330   for (i = 0, l = selfAttrs.length; i < l; i++) {
  2331.  2331   selfAttr = selfAttrs[i];
  2332.  2332  
  2333.  2333   // Clone everything except id
  2334.  2334   if (selfAttr.name !== 'id') {
  2335.  2335   cloneAttrs[cloneAttrs.length] = {name: selfAttr.name, value: selfAttr.value};
  2336.  2336   cloneAttrs.map[selfAttr.name] = selfAttr.value;
  2337.  2337   }
  2338.  2338   }
  2339.  2339  
  2340.  2340   clone.attributes = cloneAttrs;
  2341.  2341   }
  2342.  2342  
  2343.  2343   clone.value = self.value;
  2344.  2344   clone.shortEnded = self.shortEnded;
  2345.  2345  
  2346.  2346   return clone;
  2347.  2347   },
  2348.  2348  
  2349.  2349   wrap : function(wrapper) {
  2350.  2350   var self = this;
  2351.  2351  
  2352.  2352   self.parent.insert(wrapper, self);
  2353.  2353   wrapper.append(self);
  2354.  2354  
  2355.  2355   return self;
  2356.  2356   },
  2357.  2357  
  2358.  2358   unwrap : function() {
  2359.  2359   var self = this, node, next;
  2360.  2360  
  2361.  2361   for (node = self.firstChild; node; ) {
  2362.  2362   next = node.next;
  2363.  2363   self.insert(node, self, true);
  2364.  2364   node = next;
  2365.  2365   }
  2366.  2366  
  2367.  2367   self.remove();
  2368.  2368   },
  2369.  2369  
  2370.  2370   remove : function() {
  2371.  2371   var self = this, parent = self.parent, next = self.next, prev = self.prev;
  2372.  2372  
  2373.  2373   if (parent) {
  2374.  2374   if (parent.firstChild === self) {
  2375.  2375   parent.firstChild = next;
  2376.  2376  
  2377.  2377   if (next)
  2378.  2378   next.prev = null;
  2379.  2379   } else {
  2380.  2380   prev.next = next;
  2381.  2381   }
  2382.  2382  
  2383.  2383   if (parent.lastChild === self) {
  2384.  2384   parent.lastChild = prev;
  2385.  2385  
  2386.  2386   if (prev)
  2387.  2387   prev.next = null;
  2388.  2388   } else {
  2389.  2389   next.prev = prev;
  2390.  2390   }
  2391.  2391  
  2392.  2392   self.parent = self.next = self.prev = null;
  2393.  2393   }
  2394.  2394  
  2395.  2395   return self;
  2396.  2396   },
  2397.  2397  
  2398.  2398   append : function(node) {
  2399.  2399   var self = this, last;
  2400.  2400  
  2401.  2401   if (node.parent)
  2402.  2402   node.remove();
  2403.  2403  
  2404.  2404   last = self.lastChild;
  2405.  2405   if (last) {
  2406.  2406   last.next = node;
  2407.  2407   node.prev = last;
  2408.  2408   self.lastChild = node;
  2409.  2409   } else
  2410.  2410   self.lastChild = self.firstChild = node;
  2411.  2411  
  2412.  2412   node.parent = self;
  2413.  2413  
  2414.  2414   return node;
  2415.  2415   },
  2416.  2416  
  2417.  2417   insert : function(node, ref_node, before) {
  2418.  2418   var parent;
  2419.  2419  
  2420.  2420   if (node.parent)
  2421.  2421   node.remove();
  2422.  2422  
  2423.  2423   parent = ref_node.parent || this;
  2424.  2424  
  2425.  2425   if (before) {
  2426.  2426   if (ref_node === parent.firstChild)
  2427.  2427   parent.firstChild = node;
  2428.  2428   else
  2429.  2429   ref_node.prev.next = node;
  2430.  2430  
  2431.  2431   node.prev = ref_node.prev;
  2432.  2432   node.next = ref_node;
  2433.  2433   ref_node.prev = node;
  2434.  2434   } else {
  2435.  2435   if (ref_node === parent.lastChild)
  2436.  2436   parent.lastChild = node;
  2437.  2437   else
  2438.  2438   ref_node.next.prev = node;
  2439.  2439  
  2440.  2440   node.next = ref_node.next;
  2441.  2441   node.prev = ref_node;
  2442.  2442   ref_node.next = node;
  2443.  2443   }
  2444.  2444  
  2445.  2445   node.parent = parent;
  2446.  2446  
  2447.  2447   return node;
  2448.  2448   },
  2449.  2449  
  2450.  2450   getAll : function(name) {
  2451.  2451   var self = this, node, collection = [];
  2452.  2452  
  2453.  2453   for (node = self.firstChild; node; node = walk(node, self)) {
  2454.  2454   if (node.name === name)
  2455.  2455   collection.push(node);
  2456.  2456   }
  2457.  2457  
  2458.  2458   return collection;
  2459.  2459   },
  2460.  2460  
  2461.  2461   empty : function() {
  2462.  2462   var self = this, nodes, i, node;
  2463.  2463  
  2464.  2464   // Remove all children
  2465.  2465   if (self.firstChild) {
  2466.  2466   nodes = [];
  2467.  2467  
  2468.  2468   // Collect the children
  2469.  2469   for (node = self.firstChild; node; node = walk(node, self))
  2470.  2470   nodes.push(node);
  2471.  2471  
  2472.  2472   // Remove the children
  2473.  2473   i = nodes.length;
  2474.  2474   while (i--) {
  2475.  2475   node = nodes[i];
  2476.  2476   node.parent = node.firstChild = node.lastChild = node.next = node.prev = null;
  2477.  2477   }
  2478.  2478   }
  2479.  2479  
  2480.  2480   self.firstChild = self.lastChild = null;
  2481.  2481  
  2482.  2482   return self;
  2483.  2483   },
  2484.  2484  
  2485.  2485   isEmpty : function(elements) {
  2486.  2486   var self = this, node = self.firstChild, i;
  2487.  2487  
  2488.  2488   if (node) {
  2489.  2489   do {
  2490.  2490   if (node.type === 1) {
  2491.  2491   if (node.attributes.map['data-mce-bogus'])
  2492.  2492   continue;
  2493.  2493  
  2494.  2494   // Keep empty elements like <img />
  2495.  2495   if (elements[node.name])
  2496.  2496   return false;
  2497.  2497  
  2498.  2498   // Keep elements with data attributes
  2499.  2499   i = node.attributes.length;
  2500.  2500   while (i--) {
  2501.  2501   if (node.attributes[i].name.indexOf('data-') === 0)
  2502.  2502   return false;
  2503.  2503   }
  2504.  2504   }
  2505.  2505  
  2506.  2506   // Keep non whitespace text nodes
  2507.  2507   if ((node.type === 3 && !whiteSpaceRegExp.test(node.value)))
  2508.  2508   return false;
  2509.  2509   } while (node = walk(node, self));
  2510.  2510   }
  2511.  2511  
  2512.  2512   return true;
  2513.  2513   }
  2514.  2514   });
  2515.  2515  
  2516.  2516   tinymce.extend(Node, {
  2517.  2517   create : function(name, attrs) {
  2518.  2518   var node, attrName;
  2519.  2519  
  2520.  2520   // Create node
  2521.  2521   node = new Node(name, typeLookup[name] || 1);
  2522.  2522  
  2523.  2523   // Add attributes if needed
  2524.  2524   if (attrs) {
  2525.  2525   for (attrName in attrs)
  2526.  2526   node.attr(attrName, attrs[attrName]);
  2527.  2527   }
  2528.  2528  
  2529.  2529   return node;
  2530.  2530   }
  2531.  2531   });
  2532.  2532  
  2533.  2533   tinymce.html.Node = Node;
  2534.  2534  })(tinymce);
  2535.  2535  
  2536.  2536  (function(tinymce) {
  2537.  2537   var Node = tinymce.html.Node;
  2538.  2538  
  2539.  2539   tinymce.html.DomParser = function(settings, schema) {
  2540.  2540   var self = this, nodeFilters = {}, attributeFilters = [], matchedNodes = {}, matchedAttributes = {};
  2541.  2541  
  2542.  2542   settings = settings || {};
  2543.  2543   settings.root_name = settings.root_name || 'body';
  2544.  2544   self.schema = schema = schema || new tinymce.html.Schema();
  2545.  2545  
  2546.  2546   function fixInvalidChildren(nodes) {
  2547.  2547   var ni, node, parent, parents, newParent, currentNode, tempNode, childNode, i,
  2548.  2548   childClone, nonEmptyElements, nonSplitableElements, sibling, nextNode;
  2549.  2549  
  2550.  2550   nonSplitableElements = tinymce.makeMap('tr,td,th,tbody,thead,tfoot,table');
  2551.  2551   nonEmptyElements = schema.getNonEmptyElements();
  2552.  2552  
  2553.  2553   for (ni = 0; ni < nodes.length; ni++) {
  2554.  2554   node = nodes[ni];
  2555.  2555  
  2556.  2556   // Already removed
  2557.  2557   if (!node.parent)
  2558.  2558   continue;
  2559.  2559  
  2560.  2560   // Get list of all parent nodes until we find a valid parent to stick the child into
  2561.  2561   parents = [node];
  2562.  2562   for (parent = node.parent; parent && !schema.isValidChild(parent.name, node.name) && !nonSplitableElements[parent.name]; parent = parent.parent)
  2563.  2563   parents.push(parent);
  2564.  2564  
  2565.  2565   // Found a suitable parent
  2566.  2566   if (parent && parents.length > 1) {
  2567.  2567   // Reverse the array since it makes looping easier
  2568.  2568   parents.reverse();
  2569.  2569  
  2570.  2570   // Clone the related parent and insert that after the moved node
  2571.  2571   newParent = currentNode = self.filterNode(parents[0].clone());
  2572.  2572  
  2573.  2573   // Start cloning and moving children on the left side of the target node
  2574.  2574   for (i = 0; i < parents.length - 1; i++) {
  2575.  2575   if (schema.isValidChild(currentNode.name, parents[i].name)) {
  2576.  2576   tempNode = self.filterNode(parents[i].clone());
  2577.  2577   currentNode.append(tempNode);
  2578.  2578   } else
  2579.  2579   tempNode = currentNode;
  2580.  2580  
  2581.  2581   for (childNode = parents[i].firstChild; childNode && childNode != parents[i + 1]; ) {
  2582.  2582   nextNode = childNode.next;
  2583.  2583   tempNode.append(childNode);
  2584.  2584   childNode = nextNode;
  2585.  2585   }
  2586.  2586  
  2587.  2587   currentNode = tempNode;
  2588.  2588   }
  2589.  2589  
  2590.  2590   if (!newParent.isEmpty(nonEmptyElements)) {
  2591.  2591   parent.insert(newParent, parents[0], true);
  2592.  2592   parent.insert(node, newParent);
  2593.  2593   } else {
  2594.  2594   parent.insert(node, parents[0], true);
  2595.  2595   }
  2596.  2596  
  2597.  2597   // Check if the element is empty by looking through it's contents and special treatment for <p><br /></p>
  2598.  2598   parent = parents[0];
  2599.  2599   if (parent.isEmpty(nonEmptyElements) || parent.firstChild === parent.lastChild && parent.firstChild.name === 'br') {
  2600.  2600   parent.empty().remove();
  2601.  2601   }
  2602.  2602   } else if (node.parent) {
  2603.  2603   // If it's an LI try to find a UL/OL for it or wrap it
  2604.  2604   if (node.name === 'li') {
  2605.  2605   sibling = node.prev;
  2606.  2606   if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) {
  2607.  2607   sibling.append(node);
  2608.  2608   continue;
  2609.  2609   }
  2610.  2610  
  2611.  2611   sibling = node.next;
  2612.  2612   if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) {
  2613.  2613   sibling.insert(node, sibling.firstChild, true);
  2614.  2614   continue;
  2615.  2615   }
  2616.  2616  
  2617.  2617   node.wrap(self.filterNode(new Node('ul', 1)));
  2618.  2618   continue;
  2619.  2619   }
  2620.  2620  
  2621.  2621   // Try wrapping the element in a DIV
  2622.  2622   if (schema.isValidChild(node.parent.name, 'div') && schema.isValidChild('div', node.name)) {
  2623.  2623   node.wrap(self.filterNode(new Node('div', 1)));
  2624.  2624   } else {
  2625.  2625   // We failed wrapping it, then remove or unwrap it
  2626.  2626   if (node.name === 'style' || node.name === 'script')
  2627.  2627   node.empty().remove();
  2628.  2628   else
  2629.  2629   node.unwrap();
  2630.  2630   }
  2631.  2631   }
  2632.  2632   }
  2633.  2633   };
  2634.  2634  
  2635.  2635   self.filterNode = function(node) {
  2636.  2636   var i, name, list;
  2637.  2637  
  2638.  2638   // Run element filters
  2639.  2639   if (name in nodeFilters) {
  2640.  2640   list = matchedNodes[name];
  2641.  2641  
  2642.  2642   if (list)
  2643.  2643   list.push(node);
  2644.  2644   else
  2645.  2645   matchedNodes[name] = [node];
  2646.  2646   }
  2647.  2647  
  2648.  2648   // Run attribute filters
  2649.  2649   i = attributeFilters.length;
  2650.  2650   while (i--) {
  2651.  2651   name = attributeFilters[i].name;
  2652.  2652  
  2653.  2653   if (name in node.attributes.map) {
  2654.  2654   list = matchedAttributes[name];
  2655.  2655  
  2656.  2656   if (list)
  2657.  2657   list.push(node);
  2658.  2658   else
  2659.  2659   matchedAttributes[name] = [node];
  2660.  2660   }
  2661.  2661   }
  2662.  2662  
  2663.  2663   return node;
  2664.  2664   };
  2665.  2665  
  2666.  2666   self.addNodeFilter = function(name, callback) {
  2667.  2667   tinymce.each(tinymce.explode(name), function(name) {
  2668.  2668   var list = nodeFilters[name];
  2669.  2669  
  2670.  2670   if (!list)
  2671.  2671   nodeFilters[name] = list = [];
  2672.  2672  
  2673.  2673   list.push(callback);
  2674.  2674   });
  2675.  2675   };
  2676.  2676  
  2677.  2677   self.addAttributeFilter = function(name, callback) {
  2678.  2678   tinymce.each(tinymce.explode(name), function(name) {
  2679.  2679   var i;
  2680.  2680  
  2681.  2681   for (i = 0; i < attributeFilters.length; i++) {
  2682.  2682   if (attributeFilters[i].name === name) {
  2683.  2683   attributeFilters[i].callbacks.push(callback);
  2684.  2684   return;
  2685.  2685   }
  2686.  2686   }
  2687.  2687  
  2688.  2688   attributeFilters.push({name: name, callbacks: [callback]});
  2689.  2689   });
  2690.  2690   };
  2691.  2691  
  2692.  2692   self.parse = function(html, args) {
  2693.  2693   var parser, rootNode, node, nodes, i, l, fi, fl, list, name,
  2694.  2694   blockElements, startWhiteSpaceRegExp, invalidChildren = [],
  2695.  2695   endWhiteSpaceRegExp, allWhiteSpaceRegExp, whiteSpaceElements, children, nonEmptyElements;
  2696.  2696  
  2697.  2697   args = args || {};
  2698.  2698   matchedNodes = {};
  2699.  2699   matchedAttributes = {};
  2700.  2700   blockElements = tinymce.extend(tinymce.makeMap('script,style,head,title,meta,param'), schema.getBlockElements());
  2701.  2701   nonEmptyElements = schema.getNonEmptyElements();
  2702.  2702   children = schema.children;
  2703.  2703  
  2704.  2704   whiteSpaceElements = schema.getWhiteSpaceElements();
  2705.  2705   startWhiteSpaceRegExp = /^[ \t\r\n]+/;
  2706.  2706   endWhiteSpaceRegExp = /[ \t\r\n]+$/;
  2707.  2707   allWhiteSpaceRegExp = /[ \t\r\n]+/g;
  2708.  2708  
  2709.  2709   function createNode(name, type) {
  2710.  2710   var node = new Node(name, type), list;
  2711.  2711  
  2712.  2712   if (name in nodeFilters) {
  2713.  2713   list = matchedNodes[name];
  2714.  2714  
  2715.  2715   if (list)
  2716.  2716   list.push(node);
  2717.  2717   else
  2718.  2718   matchedNodes[name] = [node];
  2719.  2719   }
  2720.  2720  
  2721.  2721   return node;
  2722.  2722   };
  2723.  2723  
  2724.  2724   parser = new tinymce.html.SaxParser({
  2725.  2725   validate : settings.validate,
  2726.  2726   fix_self_closing : false, // Let the DOM parser handle <li> in <li> or <p> in <p> for better results
  2727.  2727  
  2728.  2728   cdata: function(text) {
  2729.  2729   node.append(createNode('#cdata', 4)).value = text;
  2730.  2730   },
  2731.  2731  
  2732.  2732   text: function(text, raw) {
  2733.  2733   var textNode;
  2734.  2734  
  2735.  2735   // Trim all redundant whitespace on non white space elements
  2736.  2736   if (!whiteSpaceElements[node.name]) {
  2737.  2737   text = text.replace(allWhiteSpaceRegExp, ' ');
  2738.  2738  
  2739.  2739   if (node.lastChild && blockElements[node.lastChild.name])
  2740.  2740   text = text.replace(startWhiteSpaceRegExp, '');
  2741.  2741   }
  2742.  2742  
  2743.  2743   // Do we need to create the node
  2744.  2744   if (text.length !== 0) {
  2745.  2745   textNode = createNode('#text', 3);
  2746.  2746   textNode.raw = !!raw;
  2747.  2747   node.append(textNode).value = text;
  2748.  2748   }
  2749.  2749   },
  2750.  2750  
  2751.  2751   comment: function(text) {
  2752.  2752   node.append(createNode('#comment', 8)).value = text;
  2753.  2753   },
  2754.  2754  
  2755.  2755   pi: function(name, text) {
  2756.  2756   node.append(createNode(name, 7)).value = text;
  2757.  2757   },
  2758.  2758  
  2759.  2759   doctype: function(text) {
  2760.  2760   node.append(createNode('#doctype', 10)).value = text;
  2761.  2761   },
  2762.  2762  
  2763.  2763   start: function(name, attrs, empty) {
  2764.  2764   var newNode, attrFiltersLen, elementRule, textNode, attrName, text, sibling, parent;
  2765.  2765  
  2766.  2766   elementRule = schema.getElementRule(name);
  2767.  2767   if (elementRule) {
  2768.  2768   newNode = createNode(elementRule.outputName || name, 1);
  2769.  2769   newNode.attributes = attrs;
  2770.  2770   newNode.shortEnded = empty;
  2771.  2771  
  2772.  2772   node.append(newNode);
  2773.  2773  
  2774.  2774   // Check if node is valid child of the parent node is the child is
  2775.  2775   // unknown we don't collect it since it's probably a custom element
  2776.  2776   parent = children[node.name];
  2777.  2777   if (parent && children[newNode.name] && !parent[newNode.name])
  2778.  2778   invalidChildren.push(newNode);
  2779.  2779  
  2780.  2780   attrFiltersLen = attributeFilters.length;
  2781.  2781   while (attrFiltersLen--) {
  2782.  2782   attrName = attributeFilters[attrFiltersLen].name;
  2783.  2783  
  2784.  2784   if (attrName in attrs.map) {
  2785.  2785   list = matchedAttributes[attrName];
  2786.  2786  
  2787.  2787   if (list)
  2788.  2788   list.push(newNode);
  2789.  2789   else
  2790.  2790   matchedAttributes[attrName] = [newNode];
  2791.  2791   }
  2792.  2792   }
  2793.  2793  
  2794.  2794   // Trim whitespace before block
  2795.  2795   if (blockElements[name]) {
  2796.  2796   for (textNode = newNode.prev; textNode && textNode.type === 3; ) {
  2797.  2797   text = textNode.value.replace(endWhiteSpaceRegExp, '');
  2798.  2798  
  2799.  2799   if (text.length > 0) {
  2800.  2800   textNode.value = text;
  2801.  2801   textNode = textNode.prev;
  2802.  2802   } else {
  2803.  2803   sibling = textNode.prev;
  2804.  2804   textNode.remove();
  2805.  2805   textNode = sibling;
  2806.  2806   }
  2807.  2807   }
  2808.  2808   }
  2809.  2809  
  2810.  2810   // Change current node if the element wasn't empty i.e not <br /> or <img />
  2811.  2811   if (!empty)
  2812.  2812   node = newNode;
  2813.  2813   }
  2814.  2814   },
  2815.  2815  
  2816.  2816   end: function(name) {
  2817.  2817   var textNode, elementRule, text, sibling, tempNode;
  2818.  2818  
  2819.  2819   elementRule = schema.getElementRule(name);
  2820.  2820   if (elementRule) {
  2821.  2821   if (blockElements[name]) {
  2822.  2822   if (!whiteSpaceElements[node.name]) {
  2823.  2823   // Trim whitespace at beginning of block
  2824.  2824   for (textNode = node.firstChild; textNode && textNode.type === 3; ) {
  2825.  2825   text = textNode.value.replace(startWhiteSpaceRegExp, '');
  2826.  2826  
  2827.  2827   if (text.length > 0) {
  2828.  2828   textNode.value = text;
  2829.  2829   textNode = textNode.next;
  2830.  2830   } else {
  2831.  2831   sibling = textNode.next;
  2832.  2832   textNode.remove();
  2833.  2833   textNode = sibling;
  2834.  2834   }
  2835.  2835   }
  2836.  2836  
  2837.  2837   // Trim whitespace at end of block
  2838.  2838   for (textNode = node.lastChild; textNode && textNode.type === 3; ) {
  2839.  2839   text = textNode.value.replace(endWhiteSpaceRegExp, '');
  2840.  2840  
  2841.  2841   if (text.length > 0) {
  2842.  2842   textNode.value = text;
  2843.  2843   textNode = textNode.prev;
  2844.  2844   } else {
  2845.  2845   sibling = textNode.prev;
  2846.  2846   textNode.remove();
  2847.  2847   textNode = sibling;
  2848.  2848   }
  2849.  2849   }
  2850.  2850   }
  2851.  2851  
  2852.  2852   // Trim start white space
  2853.  2853   textNode = node.prev;
  2854.  2854   if (textNode && textNode.type === 3) {
  2855.  2855   text = textNode.value.replace(startWhiteSpaceRegExp, '');
  2856.  2856  
  2857.  2857   if (text.length > 0)
  2858.  2858   textNode.value = text;
  2859.  2859   else
  2860.  2860   textNode.remove();
  2861.  2861   }
  2862.  2862   }
  2863.  2863  
  2864.  2864   // Handle empty nodes
  2865.  2865   if (elementRule.removeEmpty || elementRule.paddEmpty) {
  2866.  2866   if (node.isEmpty(nonEmptyElements)) {
  2867.  2867   if (elementRule.paddEmpty)
  2868.  2868   node.empty().append(new Node('#text', '3')).value = '\u00a0';
  2869.  2869   else {
  2870.  2870   // Leave nodes that have a name like <a name="name">
  2871.  2871   if (!node.attributes.map.name) {
  2872.  2872   tempNode = node.parent;
  2873.  2873   node.empty().remove();
  2874.  2874   node = tempNode;
  2875.  2875   return;
  2876.  2876   }
  2877.  2877   }
  2878.  2878   }
  2879.  2879   }
  2880.  2880  
  2881.  2881   node = node.parent;
  2882.  2882   }
  2883.  2883   }
  2884.  2884   }, schema);
  2885.  2885  
  2886.  2886   rootNode = node = new Node(settings.root_name, 11);
  2887.  2887  
  2888.  2888   parser.parse(html);
  2889.  2889  
  2890.  2890   fixInvalidChildren(invalidChildren);
  2891.  2891  
  2892.  2892   // Run node filters
  2893.  2893   for (name in matchedNodes) {
  2894.  2894   list = nodeFilters[name];
  2895.  2895   nodes = matchedNodes[name];
  2896.  2896  
  2897.  2897   // Remove already removed children
  2898.  2898   fi = nodes.length;
  2899.  2899   while (fi--) {
  2900.  2900   if (!nodes[fi].parent)
  2901.  2901   nodes.splice(fi, 1);
  2902.  2902   }
  2903.  2903  
  2904.  2904   for (i = 0, l = list.length; i < l; i++)
  2905.  2905   list[i](nodes, name, args);
  2906.  2906   }
  2907.  2907  
  2908.  2908   // Run attribute filters
  2909.  2909   for (i = 0, l = attributeFilters.length; i < l; i++) {
  2910.  2910   list = attributeFilters[i];
  2911.  2911  
  2912.  2912   if (list.name in matchedAttributes) {
  2913.  2913   nodes = matchedAttributes[list.name];
  2914.  2914  
  2915.  2915   // Remove already removed children
  2916.  2916   fi = nodes.length;
  2917.  2917   while (fi--) {
  2918.  2918   if (!nodes[fi].parent)
  2919.  2919   nodes.splice(fi, 1);
  2920.  2920   }
  2921.  2921  
  2922.  2922   for (fi = 0, fl = list.callbacks.length; fi < fl; fi++)
  2923.  2923   list.callbacks[fi](nodes, list.name, args);
  2924.  2924   }
  2925.  2925   }
  2926.  2926  
  2927.  2927   return rootNode;
  2928.  2928   };
  2929.  2929  
  2930.  2930   // Remove <br> at end of block elements Gecko and WebKit injects BR elements to
  2931.  2931   // make it possible to place the caret inside empty blocks. This logic tries to remove
  2932.  2932   // these elements and keep br elements that where intended to be there intact
  2933.  2933   if (settings.remove_trailing_brs) {
  2934.  2934   self.addNodeFilter('br', function(nodes, name) {
  2935.  2935   var i, l = nodes.length, node, blockElements = schema.getBlockElements(),
  2936.  2936   nonEmptyElements = schema.getNonEmptyElements(), parent, prev, prevName;
  2937.  2937  
  2938.  2938   // Must loop forwards since it will otherwise remove all brs in <p>a<br><br><br></p>
  2939.  2939   for (i = 0; i < l; i++) {
  2940.  2940   node = nodes[i];
  2941.  2941   parent = node.parent;
  2942.  2942  
  2943.  2943   if (blockElements[node.parent.name] && node === parent.lastChild) {
  2944.  2944   // Loop all nodes to the right of the current node and check for other BR elements
  2945.  2945   // excluding bookmarks since they are invisible
  2946.  2946   prev = node.prev;
  2947.  2947   while (prev) {
  2948.  2948   prevName = prev.name;
  2949.  2949  
  2950.  2950   // Ignore bookmarks
  2951.  2951   if (prevName !== "span" || prev.attr('data-mce-type') !== 'bookmark') {
  2952.  2952   // Found a non BR element
  2953.  2953   if (prevName !== "br")
  2954.  2954   break;
  2955.  2955  
  2956.  2956   // Found another br it's a <br><br> structure then don't remove anything
  2957.  2957   if (prevName === 'br') {
  2958.  2958   node = null;
  2959.  2959   break;
  2960.  2960   }
  2961.  2961   }
  2962.  2962  
  2963.  2963   prev = prev.prev;
  2964.  2964   }
  2965.  2965  
  2966.  2966   if (node) {
  2967.  2967   node.remove();
  2968.  2968  
  2969.  2969   // Is the parent to be considered empty after we removed the BR
  2970.  2970   if (parent.isEmpty(nonEmptyElements)) {
  2971.  2971   elementRule = schema.getElementRule(parent.name);
  2972.  2972  
  2973.  2973   // Remove or padd the element depending on schema rule
  2974.  2974   if (elementRule.removeEmpty)
  2975.  2975   parent.remove();
  2976.  2976   else if (elementRule.paddEmpty)
  2977.  2977   parent.empty().append(new tinymce.html.Node('#text', 3)).value = '\u00a0';
  2978.  2978   }
  2979.  2979   }
  2980.  2980   }
  2981.  2981   }
  2982.  2982   });
  2983.  2983   }
  2984.  2984   }
  2985.  2985  })(tinymce);
  2986.  2986  
  2987.  2987  tinymce.html.Writer = function(settings) {
  2988.  2988   var html = [], indent, indentBefore, indentAfter, encode, htmlOutput;
  2989.  2989  
  2990.  2990   settings = settings || {};
  2991.  2991   indent = settings.indent;
  2992.  2992   indentBefore = tinymce.makeMap(settings.indent_before || '');
  2993.  2993   indentAfter = tinymce.makeMap(settings.indent_after || '');
  2994.  2994   encode = tinymce.html.Entities.getEncodeFunc(settings.entity_encoding || 'raw', settings.entities);
  2995.  2995   htmlOutput = settings.element_format == "html";
  2996.  2996  
  2997.  2997   return {
  2998.  2998   start: function(name, attrs, empty) {
  2999.  2999   var i, l, attr, value;
  3000.  3000  
  3001.  3001   if (indent && indentBefore[name] && html.length > 0) {
  3002.  3002   value = html[html.length - 1];
  3003.  3003  
  3004.  3004   if (value.length > 0 && value !== '\n')
  3005.  3005   html.push('\n');
  3006.  3006   }
  3007.  3007  
  3008.  3008   html.push('<', name);
  3009.  3009  
  3010.  3010   if (attrs) {
  3011.  3011   for (i = 0, l = attrs.length; i < l; i++) {
  3012.  3012   attr = attrs[i];
  3013.  3013   html.push(' ', attr.name, '="', encode(attr.value, true), '"');
  3014.  3014   }
  3015.  3015   }
  3016.  3016  
  3017.  3017   if (!empty || htmlOutput)
  3018.  3018   html[html.length] = '>';
  3019.  3019   else
  3020.  3020   html[html.length] = ' />';
  3021.  3021  
  3022.  3022   /*if (indent && indentAfter[name])
  3023.  3023   html.push('\n');*/
  3024.  3024   },
  3025.  3025  
  3026.  3026   end: function(name) {
  3027.  3027   var value;
  3028.  3028  
  3029.  3029   /*if (indent && indentBefore[name] && html.length > 0) {
  3030.  3030   value = html[html.length - 1];
  3031.  3031  
  3032.  3032   if (value.length > 0 && value !== '\n')
  3033.  3033   html.push('\n');
  3034.  3034   }*/
  3035.  3035  
  3036.  3036   html.push('</', name, '>');
  3037.  3037  
  3038.  3038   if (indent && indentAfter[name] && html.length > 0) {
  3039.  3039   value = html[html.length - 1];
  3040.  3040  
  3041.  3041   if (value.length > 0 && value !== '\n')
  3042.  3042   html.push('\n');
  3043.  3043   }
  3044.  3044   },
  3045.  3045  
  3046.  3046   text: function(text, raw) {
  3047.  3047   if (text.length > 0)
  3048.  3048   html[html.length] = raw ? text : encode(text);
  3049.  3049   },
  3050.  3050  
  3051.  3051   cdata: function(text) {
  3052.  3052   html.push('<![CDATA[', text, ']]>');
  3053.  3053   },
  3054.  3054  
  3055.  3055   comment: function(text) {
  3056.  3056   html.push('<!--', text, '-->');
  3057.  3057   },
  3058.  3058  
  3059.  3059   pi: function(name, text) {
  3060.  3060   if (text)
  3061.  3061   html.push('<?', name, ' ', text, '?>');
  3062.  3062   else
  3063.  3063   html.push('<?', name, '?>');
  3064.  3064   },
  3065.  3065  
  3066.  3066   doctype: function(text) {
  3067.  3067   html.push('<!DOCTYPE', text, '>');
  3068.  3068   },
  3069.  3069  
  3070.  3070   reset: function() {
  3071.  3071   html.length = 0;
  3072.  3072   },
  3073.  3073  
  3074.  3074   getContent: function() {
  3075.  3075   return html.join('').replace(/\n$/, '');
  3076.  3076   }
  3077.  3077   };
  3078.  3078  };
  3079.  3079  
  3080.  3080  (function(tinymce) {
  3081.  3081   tinymce.html.Serializer = function(settings, schema) {
  3082.  3082   var self = this, writer = new tinymce.html.Writer(settings);
  3083.  3083  
  3084.  3084   settings = settings || {};
  3085.  3085   settings.validate = "validate" in settings ? settings.validate : true;
  3086.  3086  
  3087.  3087   self.schema = schema = schema || new tinymce.html.Schema();
  3088.  3088   self.writer = writer;
  3089.  3089  
  3090.  3090   self.serialize = function(node) {
  3091.  3091   var handlers, validate;
  3092.  3092  
  3093.  3093   validate = settings.validate;
  3094.  3094  
  3095.  3095   handlers = {
  3096.  3096   // #text
  3097.  3097   3: function(node, raw) {
  3098.  3098   writer.text(node.value, node.raw);
  3099.  3099   },
  3100.  3100  
  3101.  3101   // #comment
  3102.  3102   8: function(node) {
  3103.  3103   writer.comment(node.value);
  3104.  3104   },
  3105.  3105  
  3106.  3106   // Processing instruction
  3107.  3107   7: function(node) {
  3108.  3108   writer.pi(node.name, node.value);
  3109.  3109   },
  3110.  3110  
  3111.  3111   // Doctype
  3112.  3112   10: function(node) {
  3113.  3113   writer.doctype(node.value);
  3114.  3114   },
  3115.  3115  
  3116.  3116   // CDATA
  3117.  3117   4: function(node) {
  3118.  3118   writer.cdata(node.value);
  3119.  3119   },
  3120.  3120  
  3121.  3121   // Document fragment
  3122.  3122   11: function(node) {
  3123.  3123   if ((node = node.firstChild)) {
  3124.  3124   do {
  3125.  3125   walk(node);
  3126.  3126   } while (node = node.next);
  3127.  3127   }
  3128.  3128   }
  3129.  3129   };
  3130.  3130  
  3131.  3131   writer.reset();
  3132.  3132  
  3133.  3133   function walk(node) {
  3134.  3134   var handler = handlers[node.type], name, isEmpty, attrs, attrName, attrValue, sortedAttrs, i, l, elementRule;
  3135.  3135  
  3136.  3136   if (!handler) {
  3137.  3137   name = node.name;
  3138.  3138   isEmpty = node.shortEnded;
  3139.  3139   attrs = node.attributes;
  3140.  3140  
  3141.  3141   // Sort attributes
  3142.  3142   if (validate && attrs && attrs.length > 1) {
  3143.  3143   sortedAttrs = [];
  3144.  3144   sortedAttrs.map = {};
  3145.  3145  
  3146.  3146   elementRule = schema.getElementRule(node.name);
  3147.  3147   for (i = 0, l = elementRule.attributesOrder.length; i < l; i++) {
  3148.  3148   attrName = elementRule.attributesOrder[i];
  3149.  3149  
  3150.  3150   if (attrName in attrs.map) {
  3151.  3151   attrValue = attrs.map[attrName];
  3152.  3152   sortedAttrs.map[attrName] = attrValue;
  3153.  3153   sortedAttrs.push({name: attrName, value: attrValue});
  3154.  3154   }
  3155.  3155   }
  3156.  3156  
  3157.  3157   for (i = 0, l = attrs.length; i < l; i++) {
  3158.  3158   attrName = attrs[i].name;
  3159.  3159  
  3160.  3160   if (!(attrName in sortedAttrs.map)) {
  3161.  3161   attrValue = attrs.map[attrName];
  3162.  3162   sortedAttrs.map[attrName] = attrValue;
  3163.  3163   sortedAttrs.push({name: attrName, value: attrValue});
  3164.  3164   }
  3165.  3165   }
  3166.  3166  
  3167.  3167   attrs = sortedAttrs;
  3168.  3168   }
  3169.  3169  
  3170.  3170   writer.start(node.name, attrs, isEmpty);
  3171.  3171  
  3172.  3172   if (!isEmpty) {
  3173.  3173   if ((node = node.firstChild)) {
  3174.  3174   do {
  3175.  3175   walk(node);
  3176.  3176   } while (node = node.next);
  3177.  3177   }
  3178.  3178  
  3179.  3179   writer.end(name);
  3180.  3180   }
  3181.  3181   } else
  3182.  3182   handler(node);
  3183.  3183   }
  3184.  3184  
  3185.  3185   // Serialize element and treat all non elements as fragments
  3186.  3186   if (node.type == 1 && !settings.inner)
  3187.  3187   walk(node);
  3188.  3188   else
  3189.  3189   handlers[11](node);
  3190.  3190  
  3191.  3191   return writer.getContent();
  3192.  3192   };
  3193.  3193   }
  3194.  3194  })(tinymce);
  3195.  3195  
  3196.  3196  (function(tinymce) {
  3197.  3197   // Shorten names
  3198.  3198   var each = tinymce.each,
  3199.  3199   is = tinymce.is,
  3200.  3200   isWebKit = tinymce.isWebKit,
  3201.  3201   isIE = tinymce.isIE,
  3202.  3202   Entities = tinymce.html.Entities,
  3203.  3203   simpleSelectorRe = /^([a-z0-9],?)+$/i,
  3204.  3204   blockElementsMap = tinymce.html.Schema.blockElementsMap;
  3205.  3205  
  3206.  3206   tinymce.create('tinymce.dom.DOMUtils', {
  3207.  3207   doc : null,
  3208.  3208   root : null,
  3209.  3209   files : null,
  3210.  3210   pixelStyles : /^(top|left|bottom|right|width|height|borderWidth)$/,
  3211.  3211   props : {
  3212.  3212   "for" : "htmlFor",
  3213.  3213   "class" : "className",
  3214.  3214   className : "className",
  3215.  3215   checked : "checked",
  3216.  3216   disabled : "disabled",
  3217.  3217   maxlength : "maxLength",
  3218.  3218   readonly : "readOnly",
  3219.  3219   selected : "selected",
  3220.  3220   value : "value",
  3221.  3221   id : "id",
  3222.  3222   name : "name",
  3223.  3223   type : "type"
  3224.  3224   },
  3225.  3225  
  3226.  3226   DOMUtils : function(d, s) {
  3227.  3227   var t = this, globalStyle;
  3228.  3228  
  3229.  3229   t.doc = d;
  3230.  3230   t.win = window;
  3231.  3231   t.files = {};
  3232.  3232   t.cssFlicker = false;
  3233.  3233   t.counter = 0;
  3234.  3234   t.stdMode = !tinymce.isIE || d.documentMode >= 8;
  3235.  3235   t.boxModel = !tinymce.isIE || d.compatMode == "CSS1Compat" || t.stdMode;
  3236.  3236   t.hasOuterHTML = "outerHTML" in d.createElement("a");
  3237.  3237  
  3238.  3238   t.settings = s = tinymce.extend({
  3239.  3239   keep_values : false,
  3240.  3240   hex_colors : 1
  3241.  3241   }, s);
  3242.  3242  
  3243.  3243   t.styles = new tinymce.html.Styles({
  3244.  3244   url_converter : s.url_converter,
  3245.  3245   url_converter_scope : s.url_converter_scope
  3246.  3246   }, s.schema);
  3247.  3247  
  3248.  3248   // Fix IE6SP2 flicker and check it failed for pre SP2
  3249.  3249   if (tinymce.isIE6) {
  3250.  3250   try {
  3251.  3251   d.execCommand('BackgroundImageCache', false, true);
  3252.  3252   } catch (e) {
  3253.  3253   t.cssFlicker = true;
  3254.  3254   }
  3255.  3255   }
  3256.  3256  
  3257.  3257   if (isIE) {
  3258.  3258   // Add missing HTML 4/5 elements to IE
  3259.  3259   ('abbr article aside audio canvas ' +
  3260.  3260   'details figcaption figure footer ' +
  3261.  3261   'header hgroup mark menu meter nav ' +
  3262.  3262   'output progress section summary ' +
  3263.  3263   'time video').replace(/\w+/g, function(name) {
  3264.  3264   d.createElement(name);
  3265.  3265   });
  3266.  3266   }
  3267.  3267  
  3268.  3268   tinymce.addUnload(t.destroy, t);
  3269.  3269   },
  3270.  3270  
  3271.  3271   getRoot : function() {
  3272.  3272   var t = this, s = t.settings;
  3273.  3273  
  3274.  3274   return (s && t.get(s.root_element)) || t.doc.body;
  3275.  3275   },
  3276.  3276  
  3277.  3277   getViewPort : function(w) {
  3278.  3278   var d, b;
  3279.  3279  
  3280.  3280   w = !w ? this.win : w;
  3281.  3281   d = w.document;
  3282.  3282   b = this.boxModel ? d.documentElement : d.body;
  3283.  3283  
  3284.  3284   // Returns viewport size excluding scrollbars
  3285.  3285   return {
  3286.  3286   x : w.pageXOffset || b.scrollLeft,
  3287.  3287   y : w.pageYOffset || b.scrollTop,
  3288.  3288   w : w.innerWidth || b.clientWidth,
  3289.  3289   h : w.innerHeight || b.clientHeight
  3290.  3290   };
  3291.  3291   },
  3292.  3292  
  3293.  3293   getRect : function(e) {
  3294.  3294   var p, t = this, sr;
  3295.  3295  
  3296.  3296   e = t.get(e);
  3297.  3297   p = t.getPos(e);
  3298.  3298   sr = t.getSize(e);
  3299.  3299  
  3300.  3300   return {
  3301.  3301   x : p.x,
  3302.  3302   y : p.y,
  3303.  3303   w : sr.w,
  3304.  3304   h : sr.h
  3305.  3305   };
  3306.  3306   },
  3307.  3307  
  3308.  3308   getSize : function(e) {
  3309.  3309   var t = this, w, h;
  3310.  3310  
  3311.  3311   e = t.get(e);
  3312.  3312   w = t.getStyle(e, 'width');
  3313.  3313   h = t.getStyle(e, 'height');
  3314.  3314  
  3315.  3315   // Non pixel value, then force offset/clientWidth
  3316.  3316   if (w.indexOf('px') === -1)
  3317.  3317   w = 0;
  3318.  3318  
  3319.  3319   // Non pixel value, then force offset/clientWidth
  3320.  3320   if (h.indexOf('px') === -1)
  3321.  3321   h = 0;
  3322.  3322  
  3323.  3323   return {
  3324.  3324   w : parseInt(w) || e.offsetWidth || e.clientWidth,
  3325.  3325   h : parseInt(h) || e.offsetHeight || e.clientHeight
  3326.  3326   };
  3327.  3327   },
  3328.  3328  
  3329.  3329   getParent : function(n, f, r) {
  3330.  3330   return this.getParents(n, f, r, false);
  3331.  3331   },
  3332.  3332  
  3333.  3333   getParents : function(n, f, r, c) {
  3334.  3334   var t = this, na, se = t.settings, o = [];
  3335.  3335  
  3336.  3336   n = t.get(n);
  3337.  3337   c = c === undefined;
  3338.  3338  
  3339.  3339   if (se.strict_root)
  3340.  3340   r = r || t.getRoot();
  3341.  3341  
  3342.  3342   // Wrap node name as func
  3343.  3343   if (is(f, 'string')) {
  3344.  3344   na = f;
  3345.  3345  
  3346.  3346   if (f === '*') {
  3347.  3347   f = function(n) {return n.nodeType == 1;};
  3348.  3348   } else {
  3349.  3349   f = function(n) {
  3350.  3350   return t.is(n, na);
  3351.  3351   };
  3352.  3352   }
  3353.  3353   }
  3354.  3354  
  3355.  3355   while (n) {
  3356.  3356   if (n == r || !n.nodeType || n.nodeType === 9)
  3357.  3357   break;
  3358.  3358  
  3359.  3359   if (!f || f(n)) {
  3360.  3360   if (c)
  3361.  3361   o.push(n);
  3362.  3362   else
  3363.  3363   return n;
  3364.  3364   }
  3365.  3365  
  3366.  3366   n = n.parentNode;
  3367.  3367   }
  3368.  3368  
  3369.  3369   return c ? o : null;
  3370.  3370   },
  3371.  3371  
  3372.  3372   get : function(e) {
  3373.  3373   var n;
  3374.  3374  
  3375.  3375   if (e && this.doc && typeof(e) == 'string') {
  3376.  3376   n = e;
  3377.  3377   e = this.doc.getElementById(e);
  3378.  3378  
  3379.  3379   // IE and Opera returns meta elements when they match the specified input ID, but getElementsByName seems to do the trick
  3380.  3380   if (e && e.id !== n)
  3381.  3381   return this.doc.getElementsByName(n)[1];
  3382.  3382   }
  3383.  3383  
  3384.  3384   return e;
  3385.  3385   },
  3386.  3386  
  3387.  3387   getNext : function(node, selector) {
  3388.  3388   return this._findSib(node, selector, 'nextSibling');
  3389.  3389   },
  3390.  3390  
  3391.  3391   getPrev : function(node, selector) {
  3392.  3392   return this._findSib(node, selector, 'previousSibling');
  3393.  3393   },
  3394.  3394  
  3395.  3395  
  3396.  3396   select : function(pa, s) {
  3397.  3397   var t = this;
  3398.  3398  
  3399.  3399   return tinymce.dom.Sizzle(pa, t.get(s) || t.get(t.settings.root_element) || t.doc, []);
  3400.  3400   },
  3401.  3401  
  3402.  3402   is : function(n, selector) {
  3403.  3403   var i;
  3404.  3404  
  3405.  3405   // If it isn't an array then try to do some simple selectors instead of Sizzle for to boost performance
  3406.  3406   if (n.length === undefined) {
  3407.  3407   // Simple all selector
  3408.  3408   if (selector === '*')
  3409.  3409   return n.nodeType == 1;
  3410.  3410  
  3411.  3411   // Simple selector just elements
  3412.  3412   if (simpleSelectorRe.test(selector)) {
  3413.  3413   selector = selector.toLowerCase().split(/,/);
  3414.  3414   n = n.nodeName.toLowerCase();
  3415.  3415  
  3416.  3416   for (i = selector.length - 1; i >= 0; i--) {
  3417.  3417   if (selector[i] == n)
  3418.  3418   return true;
  3419.  3419   }
  3420.  3420  
  3421.  3421   return false;
  3422.  3422   }
  3423.  3423   }
  3424.  3424  
  3425.  3425   return tinymce.dom.Sizzle.matches(selector, n.nodeType ? [n] : n).length > 0;
  3426.  3426   },
  3427.  3427  
  3428.  3428  
  3429.  3429   add : function(p, n, a, h, c) {
  3430.  3430   var t = this;
  3431.  3431  
  3432.  3432   return this.run(p, function(p) {
  3433.  3433   var e, k;
  3434.  3434  
  3435.  3435   e = is(n, 'string') ? t.doc.createElement(n) : n;
  3436.  3436   t.setAttribs(e, a);
  3437.  3437  
  3438.  3438   if (h) {
  3439.  3439   if (h.nodeType)
  3440.  3440   e.appendChild(h);
  3441.  3441   else
  3442.  3442   t.setHTML(e, h);
  3443.  3443   }
  3444.  3444  
  3445.  3445   return !c ? p.appendChild(e) : e;
  3446.  3446   });
  3447.  3447   },
  3448.  3448  
  3449.  3449   create : function(n, a, h) {
  3450.  3450   return this.add(this.doc.createElement(n), n, a, h, 1);
  3451.  3451   },
  3452.  3452  
  3453.  3453   createHTML : function(n, a, h) {
  3454.  3454   var o = '', t = this, k;
  3455.  3455  
  3456.  3456   o += '<' + n;
  3457.  3457  
  3458.  3458   for (k in a) {
  3459.  3459   if (a.hasOwnProperty(k))
  3460.  3460   o += ' ' + k + '="' + t.encode(a[k]) + '"';
  3461.  3461   }
  3462.  3462  
  3463.  3463   // A call to tinymce.is doesn't work for some odd reason on IE9 possible bug inside their JS runtime
  3464.  3464   if (typeof(h) != "undefined")
  3465.  3465   return o + '>' + h + '</' + n + '>';
  3466.  3466  
  3467.  3467   return o + ' />';
  3468.  3468   },
  3469.  3469  
  3470.  3470   remove : function(node, keep_children) {
  3471.  3471   return this.run(node, function(node) {
  3472.  3472   var parent, child;
  3473.  3473  
  3474.  3474   parent = node.parentNode;
  3475.  3475  
  3476.  3476   if (!parent)
  3477.  3477   return null;
  3478.  3478  
  3479.  3479   if (keep_children) {
  3480.  3480   while (child = node.firstChild) {
  3481.  3481   // IE 8 will crash if you don't remove completely empty text nodes
  3482.  3482   if (!tinymce.isIE || child.nodeType !== 3 || child.nodeValue)
  3483.  3483   parent.insertBefore(child, node);
  3484.  3484   else
  3485.  3485   node.removeChild(child);
  3486.  3486   }
  3487.  3487   }
  3488.  3488  
  3489.  3489   return parent.removeChild(node);
  3490.  3490   });
  3491.  3491   },
  3492.  3492  
  3493.  3493   setStyle : function(n, na, v) {
  3494.  3494   var t = this;
  3495.  3495  
  3496.  3496   return t.run(n, function(e) {
  3497.  3497   var s, i;
  3498.  3498  
  3499.  3499   s = e.style;
  3500.  3500  
  3501.  3501   // Camelcase it, if needed
  3502.  3502   na = na.replace(/-(\D)/g, function(a, b){
  3503.  3503   return b.toUpperCase();
  3504.  3504   });
  3505.  3505  
  3506.  3506   // Default px suffix on these
  3507.  3507   if (t.pixelStyles.test(na) && (tinymce.is(v, 'number') || /^[\-0-9\.]+$/.test(v)))
  3508.  3508   v += 'px';
  3509.  3509  
  3510.  3510   switch (na) {
  3511.  3511   case 'opacity':
  3512.  3512   // IE specific opacity
  3513.  3513   if (isIE) {
  3514.  3514   s.filter = v === '' ? '' : "alpha(opacity=" + (v * 100) + ")";
  3515.  3515  
  3516.  3516   if (!n.currentStyle || !n.currentStyle.hasLayout)
  3517.  3517   s.display = 'inline-block';
  3518.  3518   }
  3519.  3519  
  3520.  3520   // Fix for older browsers
  3521.  3521   s[na] = s['-moz-opacity'] = s['-khtml-opacity'] = v || '';
  3522.  3522   break;
  3523.  3523  
  3524.  3524   case 'float':
  3525.  3525   isIE ? s.styleFloat = v : s.cssFloat = v;
  3526.  3526   break;
  3527.  3527  
  3528.  3528   default:
  3529.  3529   s[na] = v || '';
  3530.  3530   }
  3531.  3531  
  3532.  3532   // Force update of the style data
  3533.  3533   if (t.settings.update_styles)
  3534.  3534   t.setAttrib(e, 'data-mce-style');
  3535.  3535   });
  3536.  3536   },
  3537.  3537  
  3538.  3538   getStyle : function(n, na, c) {
  3539.  3539   n = this.get(n);
  3540.  3540  
  3541.  3541   if (!n)
  3542.  3542   return false;
  3543.  3543  
  3544.  3544   // Gecko
  3545.  3545   if (this.doc.defaultView && c) {
  3546.  3546   // Remove camelcase
  3547.  3547   na = na.replace(/[A-Z]/g, function(a){
  3548.  3548   return '-' + a;
  3549.  3549   });
  3550.  3550  
  3551.  3551   try {
  3552.  3552   return this.doc.defaultView.getComputedStyle(n, null).getPropertyValue(na);
  3553.  3553   } catch (ex) {
  3554.  3554   // Old safari might fail
  3555.  3555   return null;
  3556.  3556   }
  3557.  3557   }
  3558.  3558  
  3559.  3559   // Camelcase it, if needed
  3560.  3560   na = na.replace(/-(\D)/g, function(a, b){
  3561.  3561   return b.toUpperCase();
  3562.  3562   });
  3563.  3563  
  3564.  3564   if (na == 'float')
  3565.  3565   na = isIE ? 'styleFloat' : 'cssFloat';
  3566.  3566  
  3567.  3567   // IE & Opera
  3568.  3568   if (n.currentStyle && c)
  3569.  3569   return n.currentStyle[na];
  3570.  3570  
  3571.  3571   return n.style[na];
  3572.  3572   },
  3573.  3573  
  3574.  3574   setStyles : function(e, o) {
  3575.  3575   var t = this, s = t.settings, ol;
  3576.  3576  
  3577.  3577   ol = s.update_styles;
  3578.  3578   s.update_styles = 0;
  3579.  3579  
  3580.  3580   each(o, function(v, n) {
  3581.  3581   t.setStyle(e, n, v);
  3582.  3582   });
  3583.  3583  
  3584.  3584   // Update style info
  3585.  3585   s.update_styles = ol;
  3586.  3586   if (s.update_styles)
  3587.  3587   t.setAttrib(e, s.cssText);
  3588.  3588   },
  3589.  3589  
  3590.  3590   removeAllAttribs: function(e) {
  3591.  3591   return this.run(e, function(e) {
  3592.  3592   var attrs = e.attributes;
  3593.  3593   for (var i = attrs.length - 1; i >= 0; i--) {
  3594.  3594   e.removeAttributeNode(attrs.item(i));
  3595.  3595   }
  3596.  3596   });
  3597.  3597   },
  3598.  3598  
  3599.  3599   setAttrib : function(e, n, v) {
  3600.  3600   var t = this;
  3601.  3601  
  3602.  3602   // Whats the point
  3603.  3603   if (!e || !n)
  3604.  3604   return;
  3605.  3605  
  3606.  3606   // Strict XML mode
  3607.  3607   if (t.settings.strict)
  3608.  3608   n = n.toLowerCase();
  3609.  3609  
  3610.  3610   return this.run(e, function(e) {
  3611.  3611   var s = t.settings;
  3612.  3612  
  3613.  3613   switch (n) {
  3614.  3614   case "style":
  3615.  3615   if (!is(v, 'string')) {
  3616.  3616   each(v, function(v, n) {
  3617.  3617   t.setStyle(e, n, v);
  3618.  3618   });
  3619.  3619  
  3620.  3620   return;
  3621.  3621   }
  3622.  3622  
  3623.  3623   // No mce_style for elements with these since they might get resized by the user
  3624.  3624   if (s.keep_values) {
  3625.  3625   if (v && !t._isRes(v))
  3626.  3626   e.setAttribute('data-mce-style', v, 2);
  3627.  3627   else
  3628.  3628   e.removeAttribute('data-mce-style', 2);
  3629.  3629   }
  3630.  3630  
  3631.  3631   e.style.cssText = v;
  3632.  3632   break;
  3633.  3633  
  3634.  3634   case "class":
  3635.  3635   e.className = v || ''; // Fix IE null bug
  3636.  3636   break;
  3637.  3637  
  3638.  3638   case "src":
  3639.  3639   case "href":
  3640.  3640   if (s.keep_values) {
  3641.  3641   if (s.url_converter)
  3642.  3642   v = s.url_converter.call(s.url_converter_scope || t, v, n, e);
  3643.  3643  
  3644.  3644   t.setAttrib(e, 'data-mce-' + n, v, 2);
  3645.  3645   }
  3646.  3646  
  3647.  3647   break;
  3648.  3648  
  3649.  3649   case "shape":
  3650.  3650   e.setAttribute('data-mce-style', v);
  3651.  3651   break;
  3652.  3652   }
  3653.  3653  
  3654.  3654   if (is(v) && v !== null && v.length !== 0)
  3655.  3655   e.setAttribute(n, '' + v, 2);
  3656.  3656   else
  3657.  3657   e.removeAttribute(n, 2);
  3658.  3658   });
  3659.  3659   },
  3660.  3660  
  3661.  3661   setAttribs : function(e, o) {
  3662.  3662   var t = this;
  3663.  3663  
  3664.  3664   return this.run(e, function(e) {
  3665.  3665   each(o, function(v, n) {
  3666.  3666   t.setAttrib(e, n, v);
  3667.  3667   });
  3668.  3668   });
  3669.  3669   },
  3670.  3670  
  3671.  3671   getAttrib : function(e, n, dv) {
  3672.  3672   var v, t = this;
  3673.  3673  
  3674.  3674   e = t.get(e);
  3675.  3675  
  3676.  3676   if (!e || e.nodeType !== 1)
  3677.  3677   return false;
  3678.  3678  
  3679.  3679   if (!is(dv))
  3680.  3680   dv = '';
  3681.  3681  
  3682.  3682   // Try the mce variant for these
  3683.  3683   if (/^(src|href|style|coords|shape)$/.test(n)) {
  3684.  3684   v = e.getAttribute("data-mce-" + n);
  3685.  3685  
  3686.  3686   if (v)
  3687.  3687   return v;
  3688.  3688   }
  3689.  3689  
  3690.  3690   if (isIE && t.props[n]) {
  3691.  3691   v = e[t.props[n]];
  3692.  3692   v = v && v.nodeValue ? v.nodeValue : v;
  3693.  3693   }
  3694.  3694  
  3695.  3695   if (!v)
  3696.  3696   v = e.getAttribute(n, 2);
  3697.  3697  
  3698.  3698   // Check boolean attribs
  3699.  3699   if (/^(checked|compact|declare|defer|disabled|ismap|multiple|nohref|noshade|nowrap|readonly|selected)$/.test(n)) {
  3700.  3700   if (e[t.props[n]] === true && v === '')
  3701.  3701   return n;
  3702.  3702  
  3703.  3703   return v ? n : '';
  3704.  3704   }
  3705.  3705  
  3706.  3706   // Inner input elements will override attributes on form elements
  3707.  3707   if (e.nodeName === "FORM" && e.getAttributeNode(n))
  3708.  3708   return e.getAttributeNode(n).nodeValue;
  3709.  3709  
  3710.  3710   if (n === 'style') {
  3711.  3711   v = v || e.style.cssText;
  3712.  3712  
  3713.  3713   if (v) {
  3714.  3714   v = t.serializeStyle(t.parseStyle(v), e.nodeName);
  3715.  3715  
  3716.  3716   if (t.settings.keep_values && !t._isRes(v))
  3717.  3717   e.setAttribute('data-mce-style', v);
  3718.  3718   }
  3719.  3719   }
  3720.  3720  
  3721.  3721   // Remove Apple and WebKit stuff
  3722.  3722   if (isWebKit && n === "class" && v)
  3723.  3723   v = v.replace(/(apple|webkit)\-[a-z\-]+/gi, '');
  3724.  3724  
  3725.  3725   // Handle IE issues
  3726.  3726   if (isIE) {
  3727.  3727   switch (n) {
  3728.  3728   case 'rowspan':
  3729.  3729   case 'colspan':
  3730.  3730   // IE returns 1 as default value
  3731.  3731   if (v === 1)
  3732.  3732   v = '';
  3733.  3733  
  3734.  3734   break;
  3735.  3735  
  3736.  3736   case 'size':
  3737.  3737   // IE returns +0 as default value for size
  3738.  3738   if (v === '+0' || v === 20 || v === 0)
  3739.  3739   v = '';
  3740.  3740  
  3741.  3741   break;
  3742.  3742  
  3743.  3743   case 'width':
  3744.  3744   case 'height':
  3745.  3745   case 'vspace':
  3746.  3746   case 'checked':
  3747.  3747   case 'disabled':
  3748.  3748   case 'readonly':
  3749.  3749   if (v === 0)
  3750.  3750   v = '';
  3751.  3751  
  3752.  3752   break;
  3753.  3753  
  3754.  3754   case 'hspace':
  3755.  3755   // IE returns -1 as default value
  3756.  3756   if (v === -1)
  3757.  3757   v = '';
  3758.  3758  
  3759.  3759   break;
  3760.  3760  
  3761.  3761   case 'maxlength':
  3762.  3762   case 'tabindex':
  3763.  3763   // IE returns default value
  3764.  3764   if (v === 32768 || v === 2147483647 || v === '32768')
  3765.  3765   v = '';
  3766.  3766  
  3767.  3767   break;
  3768.  3768  
  3769.  3769   case 'multiple':
  3770.  3770   case 'compact':
  3771.  3771   case 'noshade':
  3772.  3772   case 'nowrap':
  3773.  3773   if (v === 65535)
  3774.  3774   return n;
  3775.  3775  
  3776.  3776   return dv;
  3777.  3777  
  3778.  3778   case 'shape':
  3779.  3779   v = v.toLowerCase();
  3780.  3780   break;
  3781.  3781  
  3782.  3782   default:
  3783.  3783   // IE has odd anonymous function for event attributes
  3784.  3784   if (n.indexOf('on') === 0 && v)
  3785.  3785   v = tinymce._replace(/^function\s+\w+\(\)\s+\{\s+(.*)\s+\}$/, '$1', '' + v);
  3786.  3786   }
  3787.  3787   }
  3788.  3788  
  3789.  3789   return (v !== undefined && v !== null && v !== '') ? '' + v : dv;
  3790.  3790   },
  3791.  3791  
  3792.  3792   getPos : function(n, ro) {
  3793.  3793   var t = this, x = 0, y = 0, e, d = t.doc, r;
  3794.  3794  
  3795.  3795   n = t.get(n);
  3796.  3796   ro = ro || d.body;
  3797.  3797  
  3798.  3798   if (n) {
  3799.  3799   // Use getBoundingClientRect on IE, Opera has it but it's not perfect
  3800.  3800   if (isIE && !t.stdMode) {
  3801.  3801   n = n.getBoundingClientRect();
  3802.  3802   e = t.boxModel ? d.documentElement : d.body;
  3803.  3803   x = t.getStyle(t.select('html')[0], 'borderWidth'); // Remove border
  3804.  3804   x = (x == 'medium' || t.boxModel && !t.isIE6) && 2 || x;
  3805.  3805  
  3806.  3806   return {x : n.left + e.scrollLeft - x, y : n.top + e.scrollTop - x};
  3807.  3807   }
  3808.  3808  
  3809.  3809   r = n;
  3810.  3810   while (r && r != ro && r.nodeType) {
  3811.  3811   x += r.offsetLeft || 0;
  3812.  3812   y += r.offsetTop || 0;
  3813.  3813   r = r.offsetParent;
  3814.  3814   }
  3815.  3815  
  3816.  3816   r = n.parentNode;
  3817.  3817   while (r && r != ro && r.nodeType) {
  3818.  3818   x -= r.scrollLeft || 0;
  3819.  3819   y -= r.scrollTop || 0;
  3820.  3820   r = r.parentNode;
  3821.  3821   }
  3822.  3822   }
  3823.  3823  
  3824.  3824   return {x : x, y : y};
  3825.  3825   },
  3826.  3826  
  3827.  3827   parseStyle : function(st) {
  3828.  3828   return this.styles.parse(st);
  3829.  3829   },
  3830.  3830  
  3831.  3831   serializeStyle : function(o, name) {
  3832.  3832   return this.styles.serialize(o, name);
  3833.  3833   },
  3834.  3834  
  3835.  3835   loadCSS : function(u) {
  3836.  3836   var t = this, d = t.doc, head;
  3837.  3837  
  3838.  3838   if (!u)
  3839.  3839   u = '';
  3840.  3840  
  3841.  3841   head = t.select('head')[0];
  3842.  3842  
  3843.  3843   each(u.split(','), function(u) {
  3844.  3844   var link;
  3845.  3845  
  3846.  3846   if (t.files[u])
  3847.  3847   return;
  3848.  3848  
  3849.  3849   t.files[u] = true;
  3850.  3850   link = t.create('link', {rel : 'stylesheet', href : tinymce._addVer(u)});
  3851.  3851  
  3852.  3852   // IE 8 has a bug where dynamically loading stylesheets would produce a 1 item remaining bug
  3853.  3853   // This fix seems to resolve that issue by realcing the document ones a stylesheet finishes loading
  3854.  3854   // It's ugly but it seems to work fine.
  3855.  3855   if (isIE && d.documentMode && d.recalc) {
  3856.  3856   link.onload = function() {
  3857.  3857   if (d.recalc)
  3858.  3858   d.recalc();
  3859.  3859  
  3860.  3860   link.onload = null;
  3861.  3861   };
  3862.  3862   }
  3863.  3863  
  3864.  3864   head.appendChild(link);
  3865.  3865   });
  3866.  3866   },
  3867.  3867  
  3868.  3868   addClass : function(e, c) {
  3869.  3869   return this.run(e, function(e) {
  3870.  3870   var o;
  3871.  3871  
  3872.  3872   if (!c)
  3873.  3873   return 0;
  3874.  3874  
  3875.  3875   if (this.hasClass(e, c))
  3876.  3876   return e.className;
  3877.  3877  
  3878.  3878   o = this.removeClass(e, c);
  3879.  3879  
  3880.  3880   return e.className = (o != '' ? (o + ' ') : '') + c;
  3881.  3881   });
  3882.  3882   },
  3883.  3883  
  3884.  3884   removeClass : function(e, c) {
  3885.  3885   var t = this, re;
  3886.  3886  
  3887.  3887   return t.run(e, function(e) {
  3888.  3888   var v;
  3889.  3889  
  3890.  3890   if (t.hasClass(e, c)) {
  3891.  3891   if (!re)
  3892.  3892   re = new RegExp("(^|\\s+)" + c + "(\\s+|$)", "g");
  3893.  3893  
  3894.  3894   v = e.className.replace(re, ' ');
  3895.  3895   v = tinymce.trim(v != ' ' ? v : '');
  3896.  3896  
  3897.  3897   e.className = v;
  3898.  3898  
  3899.  3899   // Empty class attr
  3900.  3900   if (!v) {
  3901.  3901   e.removeAttribute('class');
  3902.  3902   e.removeAttribute('className');
  3903.  3903   }
  3904.  3904  
  3905.  3905   return v;
  3906.  3906   }
  3907.  3907  
  3908.  3908   return e.className;
  3909.  3909   });
  3910.  3910   },
  3911.  3911  
  3912.  3912   hasClass : function(n, c) {
  3913.  3913   n = this.get(n);
  3914.  3914  
  3915.  3915   if (!n || !c)
  3916.  3916   return false;
  3917.  3917  
  3918.  3918   return (' ' + n.className + ' ').indexOf(' ' + c + ' ') !== -1;
  3919.  3919   },
  3920.  3920  
  3921.  3921   show : function(e) {
  3922.  3922   return this.setStyle(e, 'display', 'block');
  3923.  3923   },
  3924.  3924  
  3925.  3925   hide : function(e) {
  3926.  3926   return this.setStyle(e, 'display', 'none');
  3927.  3927   },
  3928.  3928  
  3929.  3929   isHidden : function(e) {
  3930.  3930   e = this.get(e);
  3931.  3931  
  3932.  3932   return !e || e.style.display == 'none' || this.getStyle(e, 'display') == 'none';
  3933.  3933   },
  3934.  3934  
  3935.  3935   uniqueId : function(p) {
  3936.  3936   return (!p ? 'mce_' : p) + (this.counter++);
  3937.  3937   },
  3938.  3938  
  3939.  3939   setHTML : function(element, html) {
  3940.  3940   var self = this;
  3941.  3941  
  3942.  3942   return self.run(element, function(element) {
  3943.  3943   if (isIE) {
  3944.  3944   // Remove all child nodes, IE keeps empty text nodes in DOM
  3945.  3945   while (element.firstChild)
  3946.  3946   element.removeChild(element.firstChild);
  3947.  3947  
  3948.  3948   try {
  3949.  3949   // IE will remove comments from the beginning
  3950.  3950   // unless you padd the contents with something
  3951.  3951   element.innerHTML = '<br />' + html;
  3952.  3952   element.removeChild(element.firstChild);
  3953.  3953   } catch (ex) {
  3954.  3954   // IE sometimes produces an unknown runtime error on innerHTML if it's an block element within a block element for example a div inside a p
  3955.  3955   // This seems to fix this problem
  3956.  3956  
  3957.  3957   // Create new div with HTML contents and a BR infront to keep comments
  3958.  3958   element = self.create('div');
  3959.  3959   element.innerHTML = '<br />' + html;
  3960.  3960  
  3961.  3961   // Add all children from div to target
  3962.  3962   each (element.childNodes, function(node, i) {
  3963.  3963   // Skip br element
  3964.  3964   if (i)
  3965.  3965   element.appendChild(node);
  3966.  3966   });
  3967.  3967   }
  3968.  3968   } else
  3969.  3969   element.innerHTML = html;
  3970.  3970  
  3971.  3971   return html;
  3972.  3972   });
  3973.  3973   },
  3974.  3974  
  3975.  3975   getOuterHTML : function(elm) {
  3976.  3976   var doc, self = this;
  3977.  3977  
  3978.  3978   elm = self.get(elm);
  3979.  3979  
  3980.  3980   if (!elm)
  3981.  3981   return null;
  3982.  3982  
  3983.  3983   if (elm.nodeType === 1 && self.hasOuterHTML)
  3984.  3984   return elm.outerHTML;
  3985.  3985  
  3986.  3986   doc = (elm.ownerDocument || self.doc).createElement("body");
  3987.  3987   doc.appendChild(elm.cloneNode(true));
  3988.  3988  
  3989.  3989   return doc.innerHTML;
  3990.  3990   },
  3991.  3991  
  3992.  3992   setOuterHTML : function(e, h, d) {
  3993.  3993   var t = this;
  3994.  3994  
  3995.  3995   function setHTML(e, h, d) {
  3996.  3996   var n, tp;
  3997.  3997  
  3998.  3998   tp = d.createElement("body");
  3999.  3999   tp.innerHTML = h;
  4000.  4000  
  4001.  4001   n = tp.lastChild;
  4002.  4002   while (n) {
  4003.  4003   t.insertAfter(n.cloneNode(true), e);
  4004.  4004   n = n.previousSibling;
  4005.  4005   }
  4006.  4006  
  4007.  4007   t.remove(e);
  4008.  4008   };
  4009.  4009  
  4010.  4010   return this.run(e, function(e) {
  4011.  4011   e = t.get(e);
  4012.  4012  
  4013.  4013   // Only set HTML on elements
  4014.  4014   if (e.nodeType == 1) {
  4015.  4015   d = d || e.ownerDocument || t.doc;
  4016.  4016  
  4017.  4017   if (isIE) {
  4018.  4018   try {
  4019.  4019   // Try outerHTML for IE it sometimes produces an unknown runtime error
  4020.  4020   if (isIE && e.nodeType == 1)
  4021.  4021   e.outerHTML = h;
  4022.  4022   else
  4023.  4023   setHTML(e, h, d);
  4024.  4024   } catch (ex) {
  4025.  4025   // Fix for unknown runtime error
  4026.  4026   setHTML(e, h, d);
  4027.  4027   }
  4028.  4028   } else
  4029.  4029   setHTML(e, h, d);
  4030.  4030   }
  4031.  4031   });
  4032.  4032   },
  4033.  4033  
  4034.  4034   decode : Entities.decode,
  4035.  4035  
  4036.  4036   encode : Entities.encodeAllRaw,
  4037.  4037  
  4038.  4038   insertAfter : function(node, reference_node) {
  4039.  4039   reference_node = this.get(reference_node);
  4040.  4040  
  4041.  4041   return this.run(node, function(node) {
  4042.  4042   var parent, nextSibling;
  4043.  4043  
  4044.  4044   parent = reference_node.parentNode;
  4045.  4045   nextSibling = reference_node.nextSibling;
  4046.  4046  
  4047.  4047   if (nextSibling)
  4048.  4048   parent.insertBefore(node, nextSibling);
  4049.  4049   else
  4050.  4050   parent.appendChild(node);
  4051.  4051  
  4052.  4052   return node;
  4053.  4053   });
  4054.  4054   },
  4055.  4055  
  4056.  4056   isBlock : function(node) {
  4057.  4057   var type = node.nodeType;
  4058.  4058  
  4059.  4059   // If it's a node then check the type and use the nodeName
  4060.  4060   if (type)
  4061.  4061   return !!(type === 1 && blockElementsMap[node.nodeName]);
  4062.  4062  
  4063.  4063   return !!blockElementsMap[node];
  4064.  4064   },
  4065.  4065  
  4066.  4066   replace : function(n, o, k) {
  4067.  4067   var t = this;
  4068.  4068  
  4069.  4069   if (is(o, 'array'))
  4070.  4070   n = n.cloneNode(true);
  4071.  4071  
  4072.  4072   return t.run(o, function(o) {
  4073.  4073   if (k) {
  4074.  4074   each(tinymce.grep(o.childNodes), function(c) {
  4075.  4075   n.appendChild(c);
  4076.  4076   });
  4077.  4077   }
  4078.  4078  
  4079.  4079   return o.parentNode.replaceChild(n, o);
  4080.  4080   });
  4081.  4081   },
  4082.  4082  
  4083.  4083   rename : function(elm, name) {
  4084.  4084   var t = this, newElm;
  4085.  4085  
  4086.  4086   if (elm.nodeName != name.toUpperCase()) {
  4087.  4087   // Rename block element
  4088.  4088   newElm = t.create(name);
  4089.  4089  
  4090.  4090   // Copy attribs to new block
  4091.  4091   each(t.getAttribs(elm), function(attr_node) {
  4092.  4092   t.setAttrib(newElm, attr_node.nodeName, t.getAttrib(elm, attr_node.nodeName));
  4093.  4093   });
  4094.  4094  
  4095.  4095   // Replace block
  4096.  4096   t.replace(newElm, elm, 1);
  4097.  4097   }
  4098.  4098  
  4099.  4099   return newElm || elm;
  4100.  4100   },
  4101.  4101  
  4102.  4102   findCommonAncestor : function(a, b) {
  4103.  4103   var ps = a, pe;
  4104.  4104  
  4105.  4105   while (ps) {
  4106.  4106   pe = b;
  4107.  4107  
  4108.  4108   while (pe && ps != pe)
  4109.  4109   pe = pe.parentNode;
  4110.  4110  
  4111.  4111   if (ps == pe)
  4112.  4112   break;
  4113.  4113  
  4114.  4114   ps = ps.parentNode;
  4115.  4115   }
  4116.  4116  
  4117.  4117   if (!ps && a.ownerDocument)
  4118.  4118   return a.ownerDocument.documentElement;
  4119.  4119  
  4120.  4120   return ps;
  4121.  4121   },
  4122.  4122  
  4123.  4123   toHex : function(s) {
  4124.  4124   var c = /^\s*rgb\s*?\(\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?\)\s*$/i.exec(s);
  4125.  4125  
  4126.  4126   function hex(s) {
  4127.  4127   s = parseInt(s).toString(16);
  4128.  4128  
  4129.  4129   return s.length > 1 ? s : '0' + s; // 0 -> 00
  4130.  4130   };
  4131.  4131  
  4132.  4132   if (c) {
  4133.  4133   s = '#' + hex(c[1]) + hex(c[2]) + hex(c[3]);
  4134.  4134  
  4135.  4135   return s;
  4136.  4136   }
  4137.  4137  
  4138.  4138   return s;
  4139.  4139   },
  4140.  4140  
  4141.  4141   getClasses : function() {
  4142.  4142   var t = this, cl = [], i, lo = {}, f = t.settings.class_filter, ov;
  4143.  4143  
  4144.  4144   if (t.classes)
  4145.  4145   return t.classes;
  4146.  4146  
  4147.  4147   function addClasses(s) {
  4148.  4148   // IE style imports
  4149.  4149   each(s.imports, function(r) {
  4150.  4150   addClasses(r);
  4151.  4151   });
  4152.  4152  
  4153.  4153   each(s.cssRules || s.rules, function(r) {
  4154.  4154   // Real type or fake it on IE
  4155.  4155   switch (r.type || 1) {
  4156.  4156   // Rule
  4157.  4157   case 1:
  4158.  4158   if (r.selectorText) {
  4159.  4159   each(r.selectorText.split(','), function(v) {
  4160.  4160   v = v.replace(/^\s*|\s*$|^\s\./g, "");
  4161.  4161  
  4162.  4162   // Is internal or it doesn't contain a class
  4163.  4163   if (/\.mce/.test(v) || !/\.[\w\-]+$/.test(v))
  4164.  4164   return;
  4165.  4165  
  4166.  4166   // Remove everything but class name
  4167.  4167   ov = v;
  4168.  4168   v = tinymce._replace(/.*\.([a-z0-9_\-]+).*/i, '$1', v);
  4169.  4169  
  4170.  4170   // Filter classes
  4171.  4171   if (f && !(v = f(v, ov)))
  4172.  4172   return;
  4173.  4173  
  4174.  4174   if (!lo[v]) {
  4175.  4175   cl.push({'class' : v});
  4176.  4176   lo[v] = 1;
  4177.  4177   }
  4178.  4178   });
  4179.  4179   }
  4180.  4180   break;
  4181.  4181  
  4182.  4182   // Import
  4183.  4183   case 3:
  4184.  4184   addClasses(r.styleSheet);
  4185.  4185   break;
  4186.  4186   }
  4187.  4187   });
  4188.  4188   };
  4189.  4189  
  4190.  4190   try {
  4191.  4191   each(t.doc.styleSheets, addClasses);
  4192.  4192   } catch (ex) {
  4193.  4193   // Ignore
  4194.  4194   }
  4195.  4195  
  4196.  4196   if (cl.length > 0)
  4197.  4197   t.classes = cl;
  4198.  4198  
  4199.  4199   return cl;
  4200.  4200   },
  4201.  4201  
  4202.  4202   run : function(e, f, s) {
  4203.  4203   var t = this, o;
  4204.  4204  
  4205.  4205   if (t.doc && typeof(e) === 'string')
  4206.  4206   e = t.get(e);
  4207.  4207  
  4208.  4208   if (!e)
  4209.  4209   return false;
  4210.  4210  
  4211.  4211   s = s || this;
  4212.  4212   if (!e.nodeType && (e.length || e.length === 0)) {
  4213.  4213   o = [];
  4214.  4214  
  4215.  4215   each(e, function(e, i) {
  4216.  4216   if (e) {
  4217.  4217   if (typeof(e) == 'string')
  4218.  4218   e = t.doc.getElementById(e);
  4219.  4219  
  4220.  4220   o.push(f.call(s, e, i));
  4221.  4221   }
  4222.  4222   });
  4223.  4223  
  4224.  4224   return o;
  4225.  4225   }
  4226.  4226  
  4227.  4227   return f.call(s, e);
  4228.  4228   },
  4229.  4229  
  4230.  4230   getAttribs : function(n) {
  4231.  4231   var o;
  4232.  4232  
  4233.  4233   n = this.get(n);
  4234.  4234  
  4235.  4235   if (!n)
  4236.  4236   return [];
  4237.  4237  
  4238.  4238   if (isIE) {
  4239.  4239   o = [];
  4240.  4240  
  4241.  4241   // Object will throw exception in IE
  4242.  4242   if (n.nodeName == 'OBJECT')
  4243.  4243   return n.attributes;
  4244.  4244  
  4245.  4245   // IE doesn't keep the selected attribute if you clone option elements
  4246.  4246   if (n.nodeName === 'OPTION' && this.getAttrib(n, 'selected'))
  4247.  4247   o.push({specified : 1, nodeName : 'selected'});
  4248.  4248  
  4249.  4249   // It's crazy that this is faster in IE but it's because it returns all attributes all the time
  4250.  4250   n.cloneNode(false).outerHTML.replace(/<\/?[\w:\-]+ ?|=[\"][^\"]+\"|=\'[^\']+\'|=[\w\-]+|>/gi, '').replace(/[\w:\-]+/gi, function(a) {
  4251.  4251   o.push({specified : 1, nodeName : a});
  4252.  4252   });
  4253.  4253  
  4254.  4254   return o;
  4255.  4255   }
  4256.  4256  
  4257.  4257   return n.attributes;
  4258.  4258   },
  4259.  4259  
  4260.  4260   destroy : function(s) {
  4261.  4261   var t = this;
  4262.  4262  
  4263.  4263   if (t.events)
  4264.  4264   t.events.destroy();
  4265.  4265  
  4266.  4266   t.win = t.doc = t.root = t.events = null;
  4267.  4267  
  4268.  4268   // Manual destroy then remove unload handler
  4269.  4269   if (!s)
  4270.  4270   tinymce.removeUnload(t.destroy);
  4271.  4271   },
  4272.  4272  
  4273.  4273   createRng : function() {
  4274.  4274   var d = this.doc;
  4275.  4275  
  4276.  4276   return d.createRange ? d.createRange() : new tinymce.dom.Range(this);
  4277.  4277   },
  4278.  4278  
  4279.  4279   nodeIndex : function(node, normalized) {
  4280.  4280   var idx = 0, lastNodeType, lastNode, nodeType, nodeValueExists;
  4281.  4281  
  4282.  4282   if (node) {
  4283.  4283   for (lastNodeType = node.nodeType, node = node.previousSibling, lastNode = node; node; node = node.previousSibling) {
  4284.  4284   nodeType = node.nodeType;
  4285.  4285  
  4286.  4286   // Normalize text nodes
  4287.  4287   if (normalized && nodeType == 3) {
  4288.  4288   // ensure that text nodes that have been removed are handled correctly in Internet Explorer.
  4289.  4289   // (the nodeValue attribute will not exist, and will error here).
  4290.  4290   nodeValueExists = false;
  4291.  4291   try {nodeValueExists = node.nodeValue.length} catch (c) {}
  4292.  4292   if (nodeType == lastNodeType || !nodeValueExists)
  4293.  4293   continue;
  4294.  4294   }
  4295.  4295   idx++;
  4296.  4296   lastNodeType = nodeType;
  4297.  4297   }
  4298.  4298   }
  4299.  4299  
  4300.  4300   return idx;
  4301.  4301   },
  4302.  4302  
  4303.  4303   split : function(pe, e, re) {
  4304.  4304   var t = this, r = t.createRng(), bef, aft, pa;
  4305.  4305  
  4306.  4306   // W3C valid browsers tend to leave empty nodes to the left/right side of the contents, this makes sense
  4307.  4307   // but we don't want that in our code since it serves no purpose for the end user
  4308.  4308   // For example if this is chopped:
  4309.  4309   // <p>text 1<span><b>CHOP</b></span>text 2</p>
  4310.  4310   // would produce:
  4311.  4311   // <p>text 1<span></span></p><b>CHOP</b><p><span></span>text 2</p>
  4312.  4312   // this function will then trim of empty edges and produce:
  4313.  4313   // <p>text 1</p><b>CHOP</b><p>text 2</p>
  4314.  4314   function trim(node) {
  4315.  4315   var i, children = node.childNodes;
  4316.  4316  
  4317.  4317   if (node.nodeType == 1 && node.getAttribute('data-mce-type') == 'bookmark')
  4318.  4318   return;
  4319.  4319  
  4320.  4320   for (i = children.length - 1; i >= 0; i--)
  4321.  4321   trim(children[i]);
  4322.  4322  
  4323.  4323   if (node.nodeType != 9) {
  4324.  4324   // Keep non whitespace text nodes
  4325.  4325   if (node.nodeType == 3 && node.nodeValue.length > 0) {
  4326.  4326   // If parent element isn't a block or there isn't any useful contents for example "<p> </p>"
  4327.  4327   if (!t.isBlock(node.parentNode) || tinymce.trim(node.nodeValue).length > 0)
  4328.  4328   return;
  4329.  4329   }
  4330.  4330  
  4331.  4331   if (node.nodeType == 1) {
  4332.  4332   // If the only child is a bookmark then move it up
  4333.  4333   children = node.childNodes;
  4334.  4334   if (children.length == 1 && children[0] && children[0].nodeType == 1 && children[0].getAttribute('data-mce-type') == 'bookmark')
  4335.  4335   node.parentNode.insertBefore(children[0], node);
  4336.  4336  
  4337.  4337   // Keep non empty elements or img, hr etc
  4338.  4338   if (children.length || /^(br|hr|input|img)$/i.test(node.nodeName))
  4339.  4339   return;
  4340.  4340   }
  4341.  4341  
  4342.  4342   t.remove(node);
  4343.  4343   }
  4344.  4344  
  4345.  4345   return node;
  4346.  4346   };
  4347.  4347  
  4348.  4348   if (pe && e) {
  4349.  4349   // Get before chunk
  4350.  4350   r.setStart(pe.parentNode, t.nodeIndex(pe));
  4351.  4351   r.setEnd(e.parentNode, t.nodeIndex(e));
  4352.  4352   bef = r.extractContents();
  4353.  4353  
  4354.  4354   // Get after chunk
  4355.  4355   r = t.createRng();
  4356.  4356   r.setStart(e.parentNode, t.nodeIndex(e) + 1);
  4357.  4357   r.setEnd(pe.parentNode, t.nodeIndex(pe) + 1);
  4358.  4358   aft = r.extractContents();
  4359.  4359  
  4360.  4360   // Insert before chunk
  4361.  4361   pa = pe.parentNode;
  4362.  4362   pa.insertBefore(trim(bef), pe);
  4363.  4363  
  4364.  4364   // Insert middle chunk
  4365.  4365   if (re)
  4366.  4366   pa.replaceChild(re, e);
  4367.  4367   else
  4368.  4368   pa.insertBefore(e, pe);
  4369.  4369  
  4370.  4370   // Insert after chunk
  4371.  4371   pa.insertBefore(trim(aft), pe);
  4372.  4372   t.remove(pe);
  4373.  4373  
  4374.  4374   return re || e;
  4375.  4375   }
  4376.  4376   },
  4377.  4377  
  4378.  4378   bind : function(target, name, func, scope) {
  4379.  4379   var t = this;
  4380.  4380  
  4381.  4381   if (!t.events)
  4382.  4382   t.events = new tinymce.dom.EventUtils();
  4383.  4383  
  4384.  4384   return t.events.add(target, name, func, scope || this);
  4385.  4385   },
  4386.  4386  
  4387.  4387   unbind : function(target, name, func) {
  4388.  4388   var t = this;
  4389.  4389  
  4390.  4390   if (!t.events)
  4391.  4391   t.events = new tinymce.dom.EventUtils();
  4392.  4392  
  4393.  4393   return t.events.remove(target, name, func);
  4394.  4394   },
  4395.  4395  
  4396.  4396  
  4397.  4397   _findSib : function(node, selector, name) {
  4398.  4398   var t = this, f = selector;
  4399.  4399  
  4400.  4400   if (node) {
  4401.  4401   // If expression make a function of it using is
  4402.  4402   if (is(f, 'string')) {
  4403.  4403   f = function(node) {
  4404.  4404   return t.is(node, selector);
  4405.  4405   };
  4406.  4406   }
  4407.  4407  
  4408.  4408   // Loop all siblings
  4409.  4409   for (node = node[name]; node; node = node[name]) {
  4410.  4410   if (f(node))
  4411.  4411   return node;
  4412.  4412   }
  4413.  4413   }
  4414.  4414  
  4415.  4415   return null;
  4416.  4416   },
  4417.  4417  
  4418.  4418   _isRes : function(c) {
  4419.  4419   // Is live resizble element
  4420.  4420   return /^(top|left|bottom|right|width|height)/i.test(c) || /;\s*(top|left|bottom|right|width|height)/i.test(c);
  4421.  4421   }
  4422.  4422  
  4423.  4423   /*
  4424.  4424   walk : function(n, f, s) {
  4425.  4425   var d = this.doc, w;
  4426.  4426  
  4427.  4427   if (d.createTreeWalker) {
  4428.  4428   w = d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, false);
  4429.  4429  
  4430.  4430   while ((n = w.nextNode()) != null)
  4431.  4431   f.call(s || this, n);
  4432.  4432   } else
  4433.  4433   tinymce.walk(n, f, 'childNodes', s);
  4434.  4434   }
  4435.  4435   */
  4436.  4436  
  4437.  4437   /*
  4438.  4438   toRGB : function(s) {
  4439.  4439   var c = /^\s*?#([0-9A-F]{2})([0-9A-F]{1,2})([0-9A-F]{2})?\s*?$/.exec(s);
  4440.  4440  
  4441.  4441   if (c) {
  4442.  4442   // #FFF -> #FFFFFF
  4443.  4443   if (!is(c[3]))
  4444.  4444   c[3] = c[2] = c[1];
  4445.  4445  
  4446.  4446   return "rgb(" + parseInt(c[1], 16) + "," + parseInt(c[2], 16) + "," + parseInt(c[3], 16) + ")";
  4447.  4447   }
  4448.  4448  
  4449.  4449   return s;
  4450.  4450   }
  4451.  4451   */
  4452.  4452   });
  4453.  4453  
  4454.  4454   tinymce.DOM = new tinymce.dom.DOMUtils(document, {process_html : 0});
  4455.  4455  })(tinymce);
  4456.  4456  
  4457.  4457  (function(ns) {
  4458.  4458   // Range constructor
  4459.  4459   function Range(dom) {
  4460.  4460   var t = this,
  4461.  4461   doc = dom.doc,
  4462.  4462   EXTRACT = 0,
  4463.  4463   CLONE = 1,
  4464.  4464   DELETE = 2,
  4465.  4465   TRUE = true,
  4466.  4466   FALSE = false,
  4467.  4467   START_OFFSET = 'startOffset',
  4468.  4468   START_CONTAINER = 'startContainer',
  4469.  4469   END_CONTAINER = 'endContainer',
  4470.  4470   END_OFFSET = 'endOffset',
  4471.  4471   extend = tinymce.extend,
  4472.  4472   nodeIndex = dom.nodeIndex;
  4473.  4473  
  4474.  4474   extend(t, {
  4475.  4475   // Inital states
  4476.  4476   startContainer : doc,
  4477.  4477   startOffset : 0,
  4478.  4478   endContainer : doc,
  4479.  4479   endOffset : 0,
  4480.  4480   collapsed : TRUE,
  4481.  4481   commonAncestorContainer : doc,
  4482.  4482  
  4483.  4483   // Range constants
  4484.  4484   START_TO_START : 0,
  4485.  4485   START_TO_END : 1,
  4486.  4486   END_TO_END : 2,
  4487.  4487   END_TO_START : 3,
  4488.  4488  
  4489.  4489   // Public methods
  4490.  4490   setStart : setStart,
  4491.  4491   setEnd : setEnd,
  4492.  4492   setStartBefore : setStartBefore,
  4493.  4493   setStartAfter : setStartAfter,
  4494.  4494   setEndBefore : setEndBefore,
  4495.  4495   setEndAfter : setEndAfter,
  4496.  4496   collapse : collapse,
  4497.  4497   selectNode : selectNode,
  4498.  4498   selectNodeContents : selectNodeContents,
  4499.  4499   compareBoundaryPoints : compareBoundaryPoints,
  4500.  4500   deleteContents : deleteContents,
  4501.  4501   extractContents : extractContents,
  4502.  4502   cloneContents : cloneContents,
  4503.  4503   insertNode : insertNode,
  4504.  4504   surroundContents : surroundContents,
  4505.  4505   cloneRange : cloneRange
  4506.  4506   });
  4507.  4507  
  4508.  4508   function setStart(n, o) {
  4509.  4509   _setEndPoint(TRUE, n, o);
  4510.  4510   };
  4511.  4511  
  4512.  4512   function setEnd(n, o) {
  4513.  4513   _setEndPoint(FALSE, n, o);
  4514.  4514   };
  4515.  4515  
  4516.  4516   function setStartBefore(n) {
  4517.  4517   setStart(n.parentNode, nodeIndex(n));
  4518.  4518   };
  4519.  4519  
  4520.  4520   function setStartAfter(n) {
  4521.  4521   setStart(n.parentNode, nodeIndex(n) + 1);
  4522.  4522   };
  4523.  4523  
  4524.  4524   function setEndBefore(n) {
  4525.  4525   setEnd(n.parentNode, nodeIndex(n));
  4526.  4526   };
  4527.  4527  
  4528.  4528   function setEndAfter(n) {
  4529.  4529   setEnd(n.parentNode, nodeIndex(n) + 1);
  4530.  4530   };
  4531.  4531  
  4532.  4532   function collapse(ts) {
  4533.  4533   if (ts) {
  4534.  4534   t[END_CONTAINER] = t[START_CONTAINER];
  4535.  4535   t[END_OFFSET] = t[START_OFFSET];
  4536.  4536   } else {
  4537.  4537   t[START_CONTAINER] = t[END_CONTAINER];
  4538.  4538   t[START_OFFSET] = t[END_OFFSET];
  4539.  4539   }
  4540.  4540  
  4541.  4541   t.collapsed = TRUE;
  4542.  4542   };
  4543.  4543  
  4544.  4544   function selectNode(n) {
  4545.  4545   setStartBefore(n);
  4546.  4546   setEndAfter(n);
  4547.  4547   };
  4548.  4548  
  4549.  4549   function selectNodeContents(n) {
  4550.  4550   setStart(n, 0);
  4551.  4551   setEnd(n, n.nodeType === 1 ? n.childNodes.length : n.nodeValue.length);
  4552.  4552   };
  4553.  4553  
  4554.  4554   function compareBoundaryPoints(h, r) {
  4555.  4555   var sc = t[START_CONTAINER], so = t[START_OFFSET], ec = t[END_CONTAINER], eo = t[END_OFFSET],
  4556.  4556   rsc = r.startContainer, rso = r.startOffset, rec = r.endContainer, reo = r.endOffset;
  4557.  4557  
  4558.  4558   // Check START_TO_START
  4559.  4559   if (h === 0)
  4560.  4560   return _compareBoundaryPoints(sc, so, rsc, rso);
  4561.  4561  
  4562.  4562   // Check START_TO_END
  4563.  4563   if (h === 1)
  4564.  4564   return _compareBoundaryPoints(ec, eo, rsc, rso);
  4565.  4565  
  4566.  4566   // Check END_TO_END
  4567.  4567   if (h === 2)
  4568.  4568   return _compareBoundaryPoints(ec, eo, rec, reo);
  4569.  4569  
  4570.  4570   // Check END_TO_START
  4571.  4571   if (h === 3)
  4572.  4572   return _compareBoundaryPoints(sc, so, rec, reo);
  4573.  4573   };
  4574.  4574  
  4575.  4575   function deleteContents() {
  4576.  4576   _traverse(DELETE);
  4577.  4577   };
  4578.  4578  
  4579.  4579   function extractContents() {
  4580.  4580   return _traverse(EXTRACT);
  4581.  4581   };
  4582.  4582  
  4583.  4583   function cloneContents() {
  4584.  4584   return _traverse(CLONE);
  4585.  4585   };
  4586.  4586  
  4587.  4587   function insertNode(n) {
  4588.  4588   var startContainer = this[START_CONTAINER],
  4589.  4589   startOffset = this[START_OFFSET], nn, o;
  4590.  4590  
  4591.  4591   // Node is TEXT_NODE or CDATA
  4592.  4592   if ((startContainer.nodeType === 3 || startContainer.nodeType === 4) && startContainer.nodeValue) {
  4593.  4593   if (!startOffset) {
  4594.  4594   // At the start of text
  4595.  4595   startContainer.parentNode.insertBefore(n, startContainer);
  4596.  4596   } else if (startOffset >= startContainer.nodeValue.length) {
  4597.  4597   // At the end of text
  4598.  4598   dom.insertAfter(n, startContainer);
  4599.  4599   } else {
  4600.  4600   // Middle, need to split
  4601.  4601   nn = startContainer.splitText(startOffset);
  4602.  4602   startContainer.parentNode.insertBefore(n, nn);
  4603.  4603   }
  4604.  4604   } else {
  4605.  4605   // Insert element node
  4606.  4606   if (startContainer.childNodes.length > 0)
  4607.  4607   o = startContainer.childNodes[startOffset];
  4608.  4608  
  4609.  4609   if (o)
  4610.  4610   startContainer.insertBefore(n, o);
  4611.  4611   else
  4612.  4612   startContainer.appendChild(n);
  4613.  4613   }
  4614.  4614   };
  4615.  4615  
  4616.  4616   function surroundContents(n) {
  4617.  4617   var f = t.extractContents();
  4618.  4618  
  4619.  4619   t.insertNode(n);
  4620.  4620   n.appendChild(f);
  4621.  4621   t.selectNode(n);
  4622.  4622   };
  4623.  4623  
  4624.  4624   function cloneRange() {
  4625.  4625   return extend(new Range(dom), {
  4626.  4626   startContainer : t[START_CONTAINER],
  4627.  4627   startOffset : t[START_OFFSET],
  4628.  4628   endContainer : t[END_CONTAINER],
  4629.  4629   endOffset : t[END_OFFSET],
  4630.  4630   collapsed : t.collapsed,
  4631.  4631   commonAncestorContainer : t.commonAncestorContainer
  4632.  4632   });
  4633.  4633   };
  4634.  4634  
  4635.  4635   // Private methods
  4636.  4636  
  4637.  4637   function _getSelectedNode(container, offset) {
  4638.  4638   var child;
  4639.  4639  
  4640.  4640   if (container.nodeType == 3 /* TEXT_NODE */)
  4641.  4641   return container;
  4642.  4642  
  4643.  4643   if (offset < 0)
  4644.  4644   return container;
  4645.  4645  
  4646.  4646   child = container.firstChild;
  4647.  4647   while (child && offset > 0) {
  4648.  4648   --offset;
  4649.  4649   child = child.nextSibling;
  4650.  4650   }
  4651.  4651  
  4652.  4652   if (child)
  4653.  4653   return child;
  4654.  4654  
  4655.  4655   return container;
  4656.  4656   };
  4657.  4657  
  4658.  4658   function _isCollapsed() {
  4659.  4659   return (t[START_CONTAINER] == t[END_CONTAINER] && t[START_OFFSET] == t[END_OFFSET]);
  4660.  4660   };
  4661.  4661  
  4662.  4662   function _compareBoundaryPoints(containerA, offsetA, containerB, offsetB) {
  4663.  4663   var c, offsetC, n, cmnRoot, childA, childB;
  4664.  4664  
  4665.  4665   // In the first case the boundary-points have the same container. A is before B
  4666.  4666   // if its offset is less than the offset of B, A is equal to B if its offset is
  4667.  4667   // equal to the offset of B, and A is after B if its offset is greater than the
  4668.  4668   // offset of B.
  4669.  4669   if (containerA == containerB) {
  4670.  4670   if (offsetA == offsetB)
  4671.  4671   return 0; // equal
  4672.  4672  
  4673.  4673   if (offsetA < offsetB)
  4674.  4674   return -1; // before
  4675.  4675  
  4676.  4676   return 1; // after
  4677.  4677   }
  4678.  4678  
  4679.  4679   // In the second case a child node C of the container of A is an ancestor
  4680.  4680   // container of B. In this case, A is before B if the offset of A is less than or
  4681.  4681   // equal to the index of the child node C and A is after B otherwise.
  4682.  4682   c = containerB;
  4683.  4683   while (c && c.parentNode != containerA)
  4684.  4684   c = c.parentNode;
  4685.  4685  
  4686.  4686   if (c) {
  4687.  4687   offsetC = 0;
  4688.  4688   n = containerA.firstChild;
  4689.  4689  
  4690.  4690   while (n != c && offsetC < offsetA) {
  4691.  4691   offsetC++;
  4692.  4692   n = n.nextSibling;
  4693.  4693   }
  4694.  4694  
  4695.  4695   if (offsetA <= offsetC)
  4696.  4696   return -1; // before
  4697.  4697  
  4698.  4698   return 1; // after
  4699.  4699   }
  4700.  4700  
  4701.  4701   // In the third case a child node C of the container of B is an ancestor container
  4702.  4702   // of A. In this case, A is before B if the index of the child node C is less than
  4703.  4703   // the offset of B and A is after B otherwise.
  4704.  4704   c = containerA;
  4705.  4705   while (c && c.parentNode != containerB) {
  4706.  4706   c = c.parentNode;
  4707.  4707   }
  4708.  4708  
  4709.  4709   if (c) {
  4710.  4710   offsetC = 0;
  4711.  4711   n = containerB.firstChild;
  4712.  4712  
  4713.  4713   while (n != c && offsetC < offsetB) {
  4714.  4714   offsetC++;
  4715.  4715   n = n.nextSibling;
  4716.  4716   }
  4717.  4717  
  4718.  4718   if (offsetC < offsetB)
  4719.  4719   return -1; // before
  4720.  4720  
  4721.  4721   return 1; // after
  4722.  4722   }
  4723.  4723  
  4724.  4724   // In the fourth case, none of three other cases hold: the containers of A and B
  4725.  4725   // are siblings or descendants of sibling nodes. In this case, A is before B if
  4726.  4726   // the container of A is before the container of B in a pre-order traversal of the
  4727.  4727   // Ranges' context tree and A is after B otherwise.
  4728.  4728   cmnRoot = dom.findCommonAncestor(containerA, containerB);
  4729.  4729   childA = containerA;
  4730.  4730  
  4731.  4731   while (childA && childA.parentNode != cmnRoot)
  4732.  4732   childA = childA.parentNode;
  4733.  4733  
  4734.  4734   if (!childA)
  4735.  4735   childA = cmnRoot;
  4736.  4736  
  4737.  4737   childB = containerB;
  4738.  4738   while (childB && childB.parentNode != cmnRoot)
  4739.  4739   childB = childB.parentNode;
  4740.  4740  
  4741.  4741   if (!childB)
  4742.  4742   childB = cmnRoot;
  4743.  4743  
  4744.  4744   if (childA == childB)
  4745.  4745   return 0; // equal
  4746.  4746  
  4747.  4747   n = cmnRoot.firstChild;
  4748.  4748   while (n) {
  4749.  4749   if (n == childA)
  4750.  4750   return -1; // before
  4751.  4751  
  4752.  4752   if (n == childB)
  4753.  4753   return 1; // after
  4754.  4754  
  4755.  4755   n = n.nextSibling;
  4756.  4756   }
  4757.  4757   };
  4758.  4758  
  4759.  4759   function _setEndPoint(st, n, o) {
  4760.  4760   var ec, sc;
  4761.  4761  
  4762.  4762   if (st) {
  4763.  4763   t[START_CONTAINER] = n;
  4764.  4764   t[START_OFFSET] = o;
  4765.  4765   } else {
  4766.  4766   t[END_CONTAINER] = n;
  4767.  4767   t[END_OFFSET] = o;
  4768.  4768   }
  4769.  4769  
  4770.  4770   // If one boundary-point of a Range is set to have a root container
  4771.  4771   // other than the current one for the Range, the Range is collapsed to
  4772.  4772   // the new position. This enforces the restriction that both boundary-
  4773.  4773   // points of a Range must have the same root container.
  4774.  4774   ec = t[END_CONTAINER];
  4775.  4775   while (ec.parentNode)
  4776.  4776   ec = ec.parentNode;
  4777.  4777  
  4778.  4778   sc = t[START_CONTAINER];
  4779.  4779   while (sc.parentNode)
  4780.  4780   sc = sc.parentNode;
  4781.  4781  
  4782.  4782   if (sc == ec) {
  4783.  4783   // The start position of a Range is guaranteed to never be after the
  4784.  4784   // end position. To enforce this restriction, if the start is set to
  4785.  4785   // be at a position after the end, the Range is collapsed to that
  4786.  4786   // position.
  4787.  4787   if (_compareBoundaryPoints(t[START_CONTAINER], t[START_OFFSET], t[END_CONTAINER], t[END_OFFSET]) > 0)
  4788.  4788   t.collapse(st);
  4789.  4789   } else
  4790.  4790   t.collapse(st);
  4791.  4791  
  4792.  4792   t.collapsed = _isCollapsed();
  4793.  4793   t.commonAncestorContainer = dom.findCommonAncestor(t[START_CONTAINER], t[END_CONTAINER]);
  4794.  4794   };
  4795.  4795  
  4796.  4796   function _traverse(how) {
  4797.  4797   var c, endContainerDepth = 0, startContainerDepth = 0, p, depthDiff, startNode, endNode, sp, ep;
  4798.  4798  
  4799.  4799   if (t[START_CONTAINER] == t[END_CONTAINER])
  4800.  4800   return _traverseSameContainer(how);
  4801.  4801  
  4802.  4802   for (c = t[END_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) {
  4803.  4803   if (p == t[START_CONTAINER])
  4804.  4804   return _traverseCommonStartContainer(c, how);
  4805.  4805  
  4806.  4806   ++endContainerDepth;
  4807.  4807   }
  4808.  4808  
  4809.  4809   for (c = t[START_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) {
  4810.  4810   if (p == t[END_CONTAINER])
  4811.  4811   return _traverseCommonEndContainer(c, how);
  4812.  4812  
  4813.  4813   ++startContainerDepth;
  4814.  4814   }
  4815.  4815  
  4816.  4816   depthDiff = startContainerDepth - endContainerDepth;
  4817.  4817  
  4818.  4818   startNode = t[START_CONTAINER];
  4819.  4819   while (depthDiff > 0) {
  4820.  4820   startNode = startNode.parentNode;
  4821.  4821   depthDiff--;
  4822.  4822   }
  4823.  4823  
  4824.  4824   endNode = t[END_CONTAINER];
  4825.  4825   while (depthDiff < 0) {
  4826.  4826   endNode = endNode.parentNode;
  4827.  4827   depthDiff++;
  4828.  4828   }
  4829.  4829  
  4830.  4830   // ascend the ancestor hierarchy until we have a common parent.
  4831.  4831   for (sp = startNode.parentNode, ep = endNode.parentNode; sp != ep; sp = sp.parentNode, ep = ep.parentNode) {
  4832.  4832   startNode = sp;
  4833.  4833   endNode = ep;
  4834.  4834   }
  4835.  4835  
  4836.  4836   return _traverseCommonAncestors(startNode, endNode, how);
  4837.  4837   };
  4838.  4838  
  4839.  4839   function _traverseSameContainer(how) {
  4840.  4840   var frag, s, sub, n, cnt, sibling, xferNode;
  4841.  4841  
  4842.  4842   if (how != DELETE)
  4843.  4843   frag = doc.createDocumentFragment();
  4844.  4844  
  4845.  4845   // If selection is empty, just return the fragment
  4846.  4846   if (t[START_OFFSET] == t[END_OFFSET])
  4847.  4847   return frag;
  4848.  4848  
  4849.  4849   // Text node needs special case handling
  4850.  4850   if (t[START_CONTAINER].nodeType == 3 /* TEXT_NODE */) {
  4851.  4851   // get the substring
  4852.  4852   s = t[START_CONTAINER].nodeValue;
  4853.  4853   sub = s.substring(t[START_OFFSET], t[END_OFFSET]);
  4854.  4854  
  4855.  4855   // set the original text node to its new value
  4856.  4856   if (how != CLONE) {
  4857.  4857   t[START_CONTAINER].deleteData(t[START_OFFSET], t[END_OFFSET] - t[START_OFFSET]);
  4858.  4858  
  4859.  4859   // Nothing is partially selected, so collapse to start point
  4860.  4860   t.collapse(TRUE);
  4861.  4861   }
  4862.  4862  
  4863.  4863   if (how == DELETE)
  4864.  4864   return;
  4865.  4865  
  4866.  4866   frag.appendChild(doc.createTextNode(sub));
  4867.  4867   return frag;
  4868.  4868   }
  4869.  4869  
  4870.  4870   // Copy nodes between the start/end offsets.
  4871.  4871   n = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]);
  4872.  4872   cnt = t[END_OFFSET] - t[START_OFFSET];
  4873.  4873  
  4874.  4874   while (cnt > 0) {
  4875.  4875   sibling = n.nextSibling;
  4876.  4876   xferNode = _traverseFullySelected(n, how);
  4877.  4877  
  4878.  4878   if (frag)
  4879.  4879   frag.appendChild( xferNode );
  4880.  4880  
  4881.  4881   --cnt;
  4882.  4882   n = sibling;
  4883.  4883   }
  4884.  4884  
  4885.  4885   // Nothing is partially selected, so collapse to start point
  4886.  4886   if (how != CLONE)
  4887.  4887   t.collapse(TRUE);
  4888.  4888  
  4889.  4889   return frag;
  4890.  4890   };
  4891.  4891  
  4892.  4892   function _traverseCommonStartContainer(endAncestor, how) {
  4893.  4893   var frag, n, endIdx, cnt, sibling, xferNode;
  4894.  4894  
  4895.  4895   if (how != DELETE)
  4896.  4896   frag = doc.createDocumentFragment();
  4897.  4897  
  4898.  4898   n = _traverseRightBoundary(endAncestor, how);
  4899.  4899  
  4900.  4900   if (frag)
  4901.  4901   frag.appendChild(n);
  4902.  4902  
  4903.  4903   endIdx = nodeIndex(endAncestor);
  4904.  4904   cnt = endIdx - t[START_OFFSET];
  4905.  4905  
  4906.  4906   if (cnt <= 0) {
  4907.  4907   // Collapse to just before the endAncestor, which
  4908.  4908   // is partially selected.
  4909.  4909   if (how != CLONE) {
  4910.  4910   t.setEndBefore(endAncestor);
  4911.  4911   t.collapse(FALSE);
  4912.  4912   }
  4913.  4913  
  4914.  4914   return frag;
  4915.  4915   }
  4916.  4916  
  4917.  4917   n = endAncestor.previousSibling;
  4918.  4918   while (cnt > 0) {
  4919.  4919   sibling = n.previousSibling;
  4920.  4920   xferNode = _traverseFullySelected(n, how);
  4921.  4921  
  4922.  4922   if (frag)
  4923.  4923   frag.insertBefore(xferNode, frag.firstChild);
  4924.  4924  
  4925.  4925   --cnt;
  4926.  4926   n = sibling;
  4927.  4927   }
  4928.  4928  
  4929.  4929   // Collapse to just before the endAncestor, which
  4930.  4930   // is partially selected.
  4931.  4931   if (how != CLONE) {
  4932.  4932   t.setEndBefore(endAncestor);
  4933.  4933   t.collapse(FALSE);
  4934.  4934   }
  4935.  4935  
  4936.  4936   return frag;
  4937.  4937   };
  4938.  4938  
  4939.  4939   function _traverseCommonEndContainer(startAncestor, how) {
  4940.  4940   var frag, startIdx, n, cnt, sibling, xferNode;
  4941.  4941  
  4942.  4942   if (how != DELETE)
  4943.  4943   frag = doc.createDocumentFragment();
  4944.  4944  
  4945.  4945   n = _traverseLeftBoundary(startAncestor, how);
  4946.  4946   if (frag)
  4947.  4947   frag.appendChild(n);
  4948.  4948  
  4949.  4949   startIdx = nodeIndex(startAncestor);
  4950.  4950   ++startIdx; // Because we already traversed it
  4951.  4951  
  4952.  4952   cnt = t[END_OFFSET] - startIdx;
  4953.  4953   n = startAncestor.nextSibling;
  4954.  4954   while (cnt > 0) {
  4955.  4955   sibling = n.nextSibling;
  4956.  4956   xferNode = _traverseFullySelected(n, how);
  4957.  4957  
  4958.  4958   if (frag)
  4959.  4959   frag.appendChild(xferNode);
  4960.  4960  
  4961.  4961   --cnt;
  4962.  4962   n = sibling;
  4963.  4963   }
  4964.  4964  
  4965.  4965   if (how != CLONE) {
  4966.  4966   t.setStartAfter(startAncestor);
  4967.  4967   t.collapse(TRUE);
  4968.  4968   }
  4969.  4969  
  4970.  4970   return frag;
  4971.  4971   };
  4972.  4972  
  4973.  4973   function _traverseCommonAncestors(startAncestor, endAncestor, how) {
  4974.  4974   var n, frag, commonParent, startOffset, endOffset, cnt, sibling, nextSibling;
  4975.  4975  
  4976.  4976   if (how != DELETE)
  4977.  4977   frag = doc.createDocumentFragment();
  4978.  4978  
  4979.  4979   n = _traverseLeftBoundary(startAncestor, how);
  4980.  4980   if (frag)
  4981.  4981   frag.appendChild(n);
  4982.  4982  
  4983.  4983   commonParent = startAncestor.parentNode;
  4984.  4984   startOffset = nodeIndex(startAncestor);
  4985.  4985   endOffset = nodeIndex(endAncestor);
  4986.  4986   ++startOffset;
  4987.  4987  
  4988.  4988   cnt = endOffset - startOffset;
  4989.  4989   sibling = startAncestor.nextSibling;
  4990.  4990  
  4991.  4991   while (cnt > 0) {
  4992.  4992   nextSibling = sibling.nextSibling;
  4993.  4993   n = _traverseFullySelected(sibling, how);
  4994.  4994  
  4995.  4995   if (frag)
  4996.  4996   frag.appendChild(n);
  4997.  4997  
  4998.  4998   sibling = nextSibling;
  4999.  4999   --cnt;
  5000.  5000   }
  5001.  5001  
  5002.  5002   n = _traverseRightBoundary(endAncestor, how);
  5003.  5003  
  5004.  5004   if (frag)
  5005.  5005   frag.appendChild(n);
  5006.  5006  
  5007.  5007   if (how != CLONE) {
  5008.  5008   t.setStartAfter(startAncestor);
  5009.  5009   t.collapse(TRUE);
  5010.  5010   }
  5011.  5011  
  5012.  5012   return frag;
  5013.  5013   };
  5014.  5014  
  5015.  5015   function _traverseRightBoundary(root, how) {
  5016.  5016   var next = _getSelectedNode(t[END_CONTAINER], t[END_OFFSET] - 1), parent, clonedParent, prevSibling, clonedChild, clonedGrandParent, isFullySelected = next != t[END_CONTAINER];
  5017.  5017  
  5018.  5018   if (next == root)
  5019.  5019   return _traverseNode(next, isFullySelected, FALSE, how);
  5020.  5020  
  5021.  5021   parent = next.parentNode;
  5022.  5022   clonedParent = _traverseNode(parent, FALSE, FALSE, how);
  5023.  5023  
  5024.  5024   while (parent) {
  5025.  5025   while (next) {
  5026.  5026   prevSibling = next.previousSibling;
  5027.  5027   clonedChild = _traverseNode(next, isFullySelected, FALSE, how);
  5028.  5028  
  5029.  5029   if (how != DELETE)
  5030.  5030   clonedParent.insertBefore(clonedChild, clonedParent.firstChild);
  5031.  5031  
  5032.  5032   isFullySelected = TRUE;
  5033.  5033   next = prevSibling;
  5034.  5034   }
  5035.  5035  
  5036.  5036   if (parent == root)
  5037.  5037   return clonedParent;
  5038.  5038  
  5039.  5039   next = parent.previousSibling;
  5040.  5040   parent = parent.parentNode;
  5041.  5041  
  5042.  5042   clonedGrandParent = _traverseNode(parent, FALSE, FALSE, how);
  5043.  5043  
  5044.  5044   if (how != DELETE)
  5045.  5045   clonedGrandParent.appendChild(clonedParent);
  5046.  5046  
  5047.  5047   clonedParent = clonedGrandParent;
  5048.  5048   }
  5049.  5049   };
  5050.  5050  
  5051.  5051   function _traverseLeftBoundary(root, how) {
  5052.  5052   var next = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]), isFullySelected = next != t[START_CONTAINER], parent, clonedParent, nextSibling, clonedChild, clonedGrandParent;
  5053.  5053  
  5054.  5054   if (next == root)
  5055.  5055   return _traverseNode(next, isFullySelected, TRUE, how);
  5056.  5056  
  5057.  5057   parent = next.parentNode;
  5058.  5058   clonedParent = _traverseNode(parent, FALSE, TRUE, how);
  5059.  5059  
  5060.  5060   while (parent) {
  5061.  5061   while (next) {
  5062.  5062   nextSibling = next.nextSibling;
  5063.  5063   clonedChild = _traverseNode(next, isFullySelected, TRUE, how);
  5064.  5064  
  5065.  5065   if (how != DELETE)
  5066.  5066   clonedParent.appendChild(clonedChild);
  5067.  5067  
  5068.  5068   isFullySelected = TRUE;
  5069.  5069   next = nextSibling;
  5070.  5070   }
  5071.  5071  
  5072.  5072   if (parent == root)
  5073.  5073   return clonedParent;
  5074.  5074  
  5075.  5075   next = parent.nextSibling;
  5076.  5076   parent = parent.parentNode;
  5077.  5077  
  5078.  5078   clonedGrandParent = _traverseNode(parent, FALSE, TRUE, how);
  5079.  5079  
  5080.  5080   if (how != DELETE)
  5081.  5081   clonedGrandParent.appendChild(clonedParent);
  5082.  5082  
  5083.  5083   clonedParent = clonedGrandParent;
  5084.  5084   }
  5085.  5085   };
  5086.  5086  
  5087.  5087   function _traverseNode(n, isFullySelected, isLeft, how) {
  5088.  5088   var txtValue, newNodeValue, oldNodeValue, offset, newNode;
  5089.  5089  
  5090.  5090   if (isFullySelected)
  5091.  5091   return _traverseFullySelected(n, how);
  5092.  5092  
  5093.  5093   if (n.nodeType == 3 /* TEXT_NODE */) {
  5094.  5094   txtValue = n.nodeValue;
  5095.  5095  
  5096.  5096   if (isLeft) {
  5097.  5097   offset = t[START_OFFSET];
  5098.  5098   newNodeValue = txtValue.substring(offset);
  5099.  5099   oldNodeValue = txtValue.substring(0, offset);
  5100.  5100   } else {
  5101.  5101   offset = t[END_OFFSET];
  5102.  5102   newNodeValue = txtValue.substring(0, offset);
  5103.  5103   oldNodeValue = txtValue.substring(offset);
  5104.  5104   }
  5105.  5105  
  5106.  5106   if (how != CLONE)
  5107.  5107   n.nodeValue = oldNodeValue;
  5108.  5108  
  5109.  5109   if (how == DELETE)
  5110.  5110   return;
  5111.  5111  
  5112.  5112   newNode = n.cloneNode(FALSE);
  5113.  5113   newNode.nodeValue = newNodeValue;
  5114.  5114  
  5115.  5115   return newNode;
  5116.  5116   }
  5117.  5117  
  5118.  5118   if (how == DELETE)
  5119.  5119   return;
  5120.  5120  
  5121.  5121   return n.cloneNode(FALSE);
  5122.  5122   };
  5123.  5123  
  5124.  5124   function _traverseFullySelected(n, how) {
  5125.  5125   if (how != DELETE)
  5126.  5126   return how == CLONE ? n.cloneNode(TRUE) : n;
  5127.  5127  
  5128.  5128   n.parentNode.removeChild(n);
  5129.  5129   };
  5130.  5130   };
  5131.  5131  
  5132.  5132   ns.Range = Range;
  5133.  5133  })(tinymce.dom);
  5134.  5134  
  5135.  5135  (function() {
  5136.  5136   function Selection(selection) {
  5137.  5137   var t = this, invisibleChar = '\uFEFF', range, lastIERng, dom = selection.dom, TRUE = true, FALSE = false;
  5138.  5138  
  5139.  5139   // Returns a W3C DOM compatible range object by using the IE Range API
  5140.  5140   function getRange() {
  5141.  5141   var ieRange = selection.getRng(), domRange = dom.createRng(), element, collapsed;
  5142.  5142  
  5143.  5143   // If selection is outside the current document just return an empty range
  5144.  5144   element = ieRange.item ? ieRange.item(0) : ieRange.parentElement();
  5145.  5145   if (element.ownerDocument != dom.doc)
  5146.  5146   return domRange;
  5147.  5147  
  5148.  5148   collapsed = selection.isCollapsed();
  5149.  5149  
  5150.  5150   // Handle control selection or text selection of a image
  5151.  5151   if (ieRange.item || !element.hasChildNodes()) {
  5152.  5152   if (collapsed) {
  5153.  5153   domRange.setStart(element, 0);
  5154.  5154   domRange.setEnd(element, 0);
  5155.  5155   } else {
  5156.  5156   domRange.setStart(element.parentNode, dom.nodeIndex(element));
  5157.  5157   domRange.setEnd(domRange.startContainer, domRange.startOffset + 1);
  5158.  5158   }
  5159.  5159  
  5160.  5160   return domRange;
  5161.  5161   }
  5162.  5162  
  5163.  5163   function findEndPoint(start) {
  5164.  5164   var marker, container, offset, nodes, startIndex = 0, endIndex, index, parent, checkRng, position;
  5165.  5165  
  5166.  5166   // Setup temp range and collapse it
  5167.  5167   checkRng = ieRange.duplicate();
  5168.  5168   checkRng.collapse(start);
  5169.  5169  
  5170.  5170   // Create marker and insert it at the end of the endpoints parent
  5171.  5171   marker = dom.create('a');
  5172.  5172   parent = checkRng.parentElement();
  5173.  5173  
  5174.  5174   // If parent doesn't have any children then set the container to that parent and the index to 0
  5175.  5175   if (!parent.hasChildNodes()) {
  5176.  5176   domRange[start ? 'setStart' : 'setEnd'](parent, 0);
  5177.  5177   return;
  5178.  5178   }
  5179.  5179  
  5180.  5180   parent.appendChild(marker);
  5181.  5181   checkRng.moveToElementText(marker);
  5182.  5182   position = ieRange.compareEndPoints(start ? 'StartToStart' : 'EndToEnd', checkRng);
  5183.  5183   if (position > 0) {
  5184.  5184   // The position is after the end of the parent element.
  5185.  5185   // This is the case where IE puts the caret to the left edge of a table.
  5186.  5186   domRange[start ? 'setStartAfter' : 'setEndAfter'](parent);
  5187.  5187   dom.remove(marker);
  5188.  5188   return;
  5189.  5189   }
  5190.  5190  
  5191.  5191   // Setup node list and endIndex
  5192.  5192   nodes = tinymce.grep(parent.childNodes);
  5193.  5193   endIndex = nodes.length - 1;
  5194.  5194   // Perform a binary search for the position
  5195.  5195   while (startIndex <= endIndex) {
  5196.  5196   index = Math.floor((startIndex + endIndex) / 2);
  5197.  5197  
  5198.  5198   // Insert marker and check it's position relative to the selection
  5199.  5199   parent.insertBefore(marker, nodes[index]);
  5200.  5200   checkRng.moveToElementText(marker);
  5201.  5201   position = ieRange.compareEndPoints(start ? 'StartToStart' : 'EndToEnd', checkRng);
  5202.  5202   if (position > 0) {
  5203.  5203   // Marker is to the right
  5204.  5204   startIndex = index + 1;
  5205.  5205   } else if (position < 0) {
  5206.  5206   // Marker is to the left
  5207.  5207   endIndex = index - 1;
  5208.  5208   } else {
  5209.  5209   // Maker is where we are
  5210.  5210   found = true;
  5211.  5211   break;
  5212.  5212   }
  5213.  5213   }
  5214.  5214  
  5215.  5215   // Setup container
  5216.  5216   container = position > 0 || index == 0 ? marker.nextSibling : marker.previousSibling;
  5217.  5217  
  5218.  5218   // Handle element selection
  5219.  5219   if (container.nodeType == 1) {
  5220.  5220   dom.remove(marker);
  5221.  5221  
  5222.  5222   // Find offset and container
  5223.  5223   offset = dom.nodeIndex(container);
  5224.  5224   container = container.parentNode;
  5225.  5225  
  5226.  5226   // Move the offset if we are setting the end or the position is after an element
  5227.  5227   if (!start || index > 0)
  5228.  5228   offset++;
  5229.  5229   } else {
  5230.  5230   // Calculate offset within text node
  5231.  5231   if (position > 0 || index == 0) {
  5232.  5232   checkRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', ieRange);
  5233.  5233   offset = checkRng.text.length;
  5234.  5234   } else {
  5235.  5235   checkRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', ieRange);
  5236.  5236   offset = container.nodeValue.length - checkRng.text.length;
  5237.  5237   }
  5238.  5238  
  5239.  5239   dom.remove(marker);
  5240.  5240   }
  5241.  5241  
  5242.  5242   domRange[start ? 'setStart' : 'setEnd'](container, offset);
  5243.  5243   };
  5244.  5244  
  5245.  5245   // Find start point
  5246.  5246   findEndPoint(true);
  5247.  5247  
  5248.  5248   // Find end point if needed
  5249.  5249   if (!collapsed)
  5250.  5250   findEndPoint();
  5251.  5251  
  5252.  5252   return domRange;
  5253.  5253   };
  5254.  5254  
  5255.  5255   this.addRange = function(rng) {
  5256.  5256   var ieRng, ctrlRng, startContainer, startOffset, endContainer, endOffset, doc = selection.dom.doc, body = doc.body;
  5257.  5257  
  5258.  5258   function setEndPoint(start) {
  5259.  5259   var container, offset, marker, tmpRng, nodes;
  5260.  5260  
  5261.  5261   marker = dom.create('a');
  5262.  5262   container = start ? startContainer : endContainer;
  5263.  5263   offset = start ? startOffset : endOffset;
  5264.  5264   tmpRng = ieRng.duplicate();
  5265.  5265  
  5266.  5266   if (container == doc || container == doc.documentElement) {
  5267.  5267   container = body;
  5268.  5268   offset = 0;
  5269.  5269   }
  5270.  5270  
  5271.  5271   if (container.nodeType == 3) {
  5272.  5272   container.parentNode.insertBefore(marker, container);
  5273.  5273   tmpRng.moveToElementText(marker);
  5274.  5274   tmpRng.moveStart('character', offset);
  5275.  5275   dom.remove(marker);
  5276.  5276   ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng);
  5277.  5277   } else {
  5278.  5278   nodes = container.childNodes;
  5279.  5279  
  5280.  5280   if (nodes.length) {
  5281.  5281   if (offset >= nodes.length) {
  5282.  5282   dom.insertAfter(marker, nodes[nodes.length - 1]);
  5283.  5283   } else {
  5284.  5284   container.insertBefore(marker, nodes[offset]);
  5285.  5285   }
  5286.  5286  
  5287.  5287   tmpRng.moveToElementText(marker);
  5288.  5288   } else {
  5289.  5289   // Empty node selection for example <div>|</div>
  5290.  5290   marker = doc.createTextNode(invisibleChar);
  5291.  5291   container.appendChild(marker);
  5292.  5292   tmpRng.moveToElementText(marker.parentNode);
  5293.  5293   tmpRng.collapse(TRUE);
  5294.  5294   }
  5295.  5295  
  5296.  5296   ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng);
  5297.  5297   dom.remove(marker);
  5298.  5298   }
  5299.  5299   }
  5300.  5300  
  5301.  5301   // Destroy cached range
  5302.  5302   this.destroy();
  5303.  5303  
  5304.  5304   // Setup some shorter versions
  5305.  5305   startContainer = rng.startContainer;
  5306.  5306   startOffset = rng.startOffset;
  5307.  5307   endContainer = rng.endContainer;
  5308.  5308   endOffset = rng.endOffset;
  5309.  5309   ieRng = body.createTextRange();
  5310.  5310  
  5311.  5311   // If single element selection then try making a control selection out of it
  5312.  5312   if (startContainer == endContainer && startContainer.nodeType == 1 && startOffset == endOffset - 1) {
  5313.  5313   if (startOffset == endOffset - 1) {
  5314.  5314   try {
  5315.  5315   ctrlRng = body.createControlRange();
  5316.  5316   ctrlRng.addElement(startContainer.childNodes[startOffset]);
  5317.  5317   ctrlRng.select();
  5318.  5318   return;
  5319.  5319   } catch (ex) {
  5320.  5320   // Ignore
  5321.  5321   }
  5322.  5322   }
  5323.  5323   }
  5324.  5324  
  5325.  5325   // Set start/end point of selection
  5326.  5326   setEndPoint(true);
  5327.  5327   setEndPoint();
  5328.  5328  
  5329.  5329   // Select the new range and scroll it into view
  5330.  5330   ieRng.select();
  5331.  5331   };
  5332.  5332  
  5333.  5333   this.getRangeAt = function() {
  5334.  5334   // Setup new range if the cache is empty
  5335.  5335   if (!range || !tinymce.dom.RangeUtils.compareRanges(lastIERng, selection.getRng())) {
  5336.  5336   range = getRange();
  5337.  5337  
  5338.  5338   // Store away text range for next call
  5339.  5339   lastIERng = selection.getRng();
  5340.  5340   }
  5341.  5341  
  5342.  5342   // IE will say that the range is equal then produce an invalid argument exception
  5343.  5343   // if you perform specific operations in a keyup event. For example Ctrl+Del.
  5344.  5344   // This hack will invalidate the range cache if the exception occurs
  5345.  5345   try {
  5346.  5346   range.startContainer.nextSibling;
  5347.  5347   } catch (ex) {
  5348.  5348   range = getRange();
  5349.  5349   lastIERng = null;
  5350.  5350   }
  5351.  5351  
  5352.  5352   // Return cached range
  5353.  5353   return range;
  5354.  5354   };
  5355.  5355  
  5356.  5356   this.destroy = function() {
  5357.  5357   // Destroy cached range and last IE range to avoid memory leaks
  5358.  5358   lastIERng = range = null;
  5359.  5359   };
  5360.  5360   };
  5361.  5361  
  5362.  5362   // Expose the selection object
  5363.  5363   tinymce.dom.TridentSelection = Selection;
  5364.  5364  })();
  5365.  5365  
  5366.  5366  
  5367.  5367  /*
  5368.  5368   * Sizzle CSS Selector Engine - v1.0
  5369.  5369   * Copyright 2009, The Dojo Foundation
  5370.  5370   * Released under the MIT, BSD, and GPL Licenses.
  5371.  5371   * More information: http://sizzlejs.com/
  5372.  5372   */
  5373.  5373  (function(){
  5374.  5374  
  5375.  5375  var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,
  5376.  5376   done = 0,
  5377.  5377   toString = Object.prototype.toString,
  5378.  5378   hasDuplicate = false,
  5379.  5379   baseHasDuplicate = true;
  5380.  5380  
  5381.  5381  // Here we check if the JavaScript engine is using some sort of
  5382.  5382  // optimization where it does not always call our comparision
  5383.  5383  // function. If that is the case, discard the hasDuplicate value.
  5384.  5384  // Thus far that includes Google Chrome.
  5385.  5385  [0, 0].sort(function(){
  5386.  5386   baseHasDuplicate = false;
  5387.  5387   return 0;
  5388.  5388  });
  5389.  5389  
  5390.  5390  var Sizzle = function(selector, context, results, seed) {
  5391.  5391   results = results || [];
  5392.  5392   context = context || document;
  5393.  5393  
  5394.  5394   var origContext = context;
  5395.  5395  
  5396.  5396   if ( context.nodeType !== 1 && context.nodeType !== 9 ) {
  5397.  5397   return [];
  5398.  5398   }
  5399.  5399  
  5400.  5400   if ( !selector || typeof selector !== "string" ) {
  5401.  5401   return results;
  5402.  5402   }
  5403.  5403  
  5404.  5404   var parts = [], m, set, checkSet, extra, prune = true, contextXML = Sizzle.isXML(context),
  5405.  5405   soFar = selector, ret, cur, pop, i;
  5406.  5406  
  5407.  5407   // Reset the position of the chunker regexp (start from head)
  5408.  5408   do {
  5409.  5409   chunker.exec("");
  5410.  5410   m = chunker.exec(soFar);
  5411.  5411  
  5412.  5412   if ( m ) {
  5413.  5413   soFar = m[3];
  5414.  5414  
  5415.  5415   parts.push( m[1] );
  5416.  5416  
  5417.  5417   if ( m[2] ) {
  5418.  5418   extra = m[3];
  5419.  5419   break;
  5420.  5420   }
  5421.  5421   }
  5422.  5422   } while ( m );
  5423.  5423  
  5424.  5424   if ( parts.length > 1 && origPOS.exec( selector ) ) {
  5425.  5425   if ( parts.length === 2 && Expr.relative[ parts[0] ] ) {
  5426.  5426   set = posProcess( parts[0] + parts[1], context );
  5427.  5427   } else {
  5428.  5428   set = Expr.relative[ parts[0] ] ?
  5429.  5429   [ context ] :
  5430.  5430   Sizzle( parts.shift(), context );
  5431.  5431  
  5432.  5432   while ( parts.length ) {
  5433.  5433   selector = parts.shift();
  5434.  5434  
  5435.  5435   if ( Expr.relative[ selector ] ) {
  5436.  5436   selector += parts.shift();
  5437.  5437   }
  5438.  5438  
  5439.  5439   set = posProcess( selector, set );
  5440.  5440   }
  5441.  5441   }
  5442.  5442   } else {
  5443.  5443   // Take a shortcut and set the context if the root selector is an ID
  5444.  5444   // (but not if it'll be faster if the inner selector is an ID)
  5445.  5445   if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML &&
  5446.  5446   Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) {
  5447.  5447   ret = Sizzle.find( parts.shift(), context, contextXML );
  5448.  5448   context = ret.expr ? Sizzle.filter( ret.expr, ret.set )[0] : ret.set[0];
  5449.  5449   }
  5450.  5450  
  5451.  5451   if ( context ) {
  5452.  5452   ret = seed ?
  5453.  5453   { expr: parts.pop(), set: makeArray(seed) } :
  5454.  5454   Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML );
  5455.  5455   set = ret.expr ? Sizzle.filter( ret.expr, ret.set ) : ret.set;
  5456.  5456  
  5457.  5457   if ( parts.length > 0 ) {
  5458.  5458   checkSet = makeArray(set);
  5459.  5459   } else {
  5460.  5460   prune = false;
  5461.  5461   }
  5462.  5462  
  5463.  5463   while ( parts.length ) {
  5464.  5464   cur = parts.pop();
  5465.  5465   pop = cur;
  5466.  5466  
  5467.  5467   if ( !Expr.relative[ cur ] ) {
  5468.  5468   cur = "";
  5469.  5469   } else {
  5470.  5470   pop = parts.pop();
  5471.  5471   }
  5472.  5472  
  5473.  5473   if ( pop == null ) {
  5474.  5474   pop = context;
  5475.  5475   }
  5476.  5476  
  5477.  5477   Expr.relative[ cur ]( checkSet, pop, contextXML );
  5478.  5478   }
  5479.  5479   } else {
  5480.  5480   checkSet = parts = [];
  5481.  5481   }
  5482.  5482   }
  5483.  5483  
  5484.  5484   if ( !checkSet ) {
  5485.  5485   checkSet = set;
  5486.  5486   }
  5487.  5487  
  5488.  5488   if ( !checkSet ) {
  5489.  5489   Sizzle.error( cur || selector );
  5490.  5490   }
  5491.  5491  
  5492.  5492   if ( toString.call(checkSet) === "[object Array]" ) {
  5493.  5493   if ( !prune ) {
  5494.  5494   results.push.apply( results, checkSet );
  5495.  5495   } else if ( context && context.nodeType === 1 ) {
  5496.  5496   for ( i = 0; checkSet[i] != null; i++ ) {
  5497.  5497   if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i])) ) {
  5498.  5498   results.push( set[i] );
  5499.  5499   }
  5500.  5500   }
  5501.  5501   } else {
  5502.  5502   for ( i = 0; checkSet[i] != null; i++ ) {
  5503.  5503   if ( checkSet[i] && checkSet[i].nodeType === 1 ) {
  5504.  5504   results.push( set[i] );
  5505.  5505   }
  5506.  5506   }
  5507.  5507   }
  5508.  5508   } else {
  5509.  5509   makeArray( checkSet, results );
  5510.  5510   }
  5511.  5511  
  5512.  5512   if ( extra ) {
  5513.  5513   Sizzle( extra, origContext, results, seed );
  5514.  5514   Sizzle.uniqueSort( results );
  5515.  5515   }
  5516.  5516  
  5517.  5517   return results;
  5518.  5518  };
  5519.  5519  
  5520.  5520  Sizzle.uniqueSort = function(results){
  5521.  5521   if ( sortOrder ) {
  5522.  5522   hasDuplicate = baseHasDuplicate;
  5523.  5523   results.sort(sortOrder);
  5524.  5524  
  5525.  5525   if ( hasDuplicate ) {
  5526.  5526   for ( var i = 1; i < results.length; i++ ) {
  5527.  5527   if ( results[i] === results[i-1] ) {
  5528.  5528   results.splice(i--, 1);
  5529.  5529   }
  5530.  5530   }
  5531.  5531   }
  5532.  5532   }
  5533.  5533  
  5534.  5534   return results;
  5535.  5535  };
  5536.  5536  
  5537.  5537  Sizzle.matches = function(expr, set){
  5538.  5538   return Sizzle(expr, null, null, set);
  5539.  5539  };
  5540.  5540  
  5541.  5541  Sizzle.find = function(expr, context, isXML){
  5542.  5542   var set;
  5543.  5543  
  5544.  5544   if ( !expr ) {
  5545.  5545   return [];
  5546.  5546   }
  5547.  5547  
  5548.  5548   for ( var i = 0, l = Expr.order.length; i < l; i++ ) {
  5549.  5549   var type = Expr.order[i], match;
  5550.  5550  
  5551.  5551   if ( (match = Expr.leftMatch[ type ].exec( expr )) ) {
  5552.  5552   var left = match[1];
  5553.  5553   match.splice(1,1);
  5554.  5554  
  5555.  5555   if ( left.substr( left.length - 1 ) !== "\\" ) {
  5556.  5556   match[1] = (match[1] || "").replace(/\\/g, "");
  5557.  5557   set = Expr.find[ type ]( match, context, isXML );
  5558.  5558   if ( set != null ) {
  5559.  5559   expr = expr.replace( Expr.match[ type ], "" );
  5560.  5560   break;
  5561.  5561   }
  5562.  5562   }
  5563.  5563   }
  5564.  5564   }
  5565.  5565  
  5566.  5566   if ( !set ) {
  5567.  5567   set = context.getElementsByTagName("*");
  5568.  5568   }
  5569.  5569  
  5570.  5570   return {set: set, expr: expr};
  5571.  5571  };
  5572.  5572  
  5573.  5573  Sizzle.filter = function(expr, set, inplace, not){
  5574.  5574   var old = expr, result = [], curLoop = set, match, anyFound,
  5575.  5575   isXMLFilter = set && set[0] && Sizzle.isXML(set[0]);
  5576.  5576  
  5577.  5577   while ( expr && set.length ) {
  5578.  5578   for ( var type in Expr.filter ) {
  5579.  5579   if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) {
  5580.  5580   var filter = Expr.filter[ type ], found, item, left = match[1];
  5581.  5581   anyFound = false;
  5582.  5582  
  5583.  5583   match.splice(1,1);
  5584.  5584  
  5585.  5585   if ( left.substr( left.length - 1 ) === "\\" ) {
  5586.  5586   continue;
  5587.  5587   }
  5588.  5588  
  5589.  5589   if ( curLoop === result ) {
  5590.  5590   result = [];
  5591.  5591   }
  5592.  5592  
  5593.  5593   if ( Expr.preFilter[ type ] ) {
  5594.  5594   match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter );
  5595.  5595  
  5596.  5596   if ( !match ) {
  5597.  5597   anyFound = found = true;
  5598.  5598   } else if ( match === true ) {
  5599.  5599   continue;
  5600.  5600   }
  5601.  5601   }
  5602.  5602  
  5603.  5603   if ( match ) {
  5604.  5604   for ( var i = 0; (item = curLoop[i]) != null; i++ ) {
  5605.  5605   if ( item ) {
  5606.  5606   found = filter( item, match, i, curLoop );
  5607.  5607   var pass = not ^ !!found;
  5608.  5608  
  5609.  5609   if ( inplace && found != null ) {
  5610.  5610   if ( pass ) {
  5611.  5611   anyFound = true;
  5612.  5612   } else {
  5613.  5613   curLoop[i] = false;
  5614.  5614   }
  5615.  5615   } else if ( pass ) {
  5616.  5616   result.push( item );
  5617.  5617   anyFound = true;
  5618.  5618   }
  5619.  5619   }
  5620.  5620   }
  5621.  5621   }
  5622.  5622  
  5623.  5623   if ( found !== undefined ) {
  5624.  5624   if ( !inplace ) {
  5625.  5625   curLoop = result;
  5626.  5626   }
  5627.  5627  
  5628.  5628   expr = expr.replace( Expr.match[ type ], "" );
  5629.  5629  
  5630.  5630   if ( !anyFound ) {
  5631.  5631   return [];
  5632.  5632   }
  5633.  5633  
  5634.  5634   break;
  5635.  5635   }
  5636.  5636   }
  5637.  5637   }
  5638.  5638  
  5639.  5639   // Improper expression
  5640.  5640   if ( expr === old ) {
  5641.  5641   if ( anyFound == null ) {
  5642.  5642   Sizzle.error( expr );
  5643.  5643   } else {
  5644.  5644   break;
  5645.  5645   }
  5646.  5646   }
  5647.  5647  
  5648.  5648   old = expr;
  5649.  5649   }
  5650.  5650  
  5651.  5651   return curLoop;
  5652.  5652  };
  5653.  5653  
  5654.  5654  Sizzle.error = function( msg ) {
  5655.  5655   throw "Syntax error, unrecognized expression: " + msg;
  5656.  5656  };
  5657.  5657  
  5658.  5658  var Expr = Sizzle.selectors = {
  5659.  5659   order: [ "ID", "NAME", "TAG" ],
  5660.  5660   match: {
  5661.  5661   ID: /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,
  5662.  5662   CLASS: /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,
  5663.  5663   NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/,
  5664.  5664   ATTR: /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/,
  5665.  5665   TAG: /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/,
  5666.  5666   CHILD: /:(only|nth|last|first)-child(?:\((even|odd|[\dn+\-]*)\))?/,
  5667.  5667   POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/,
  5668.  5668   PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/
  5669.  5669   },
  5670.  5670   leftMatch: {},
  5671.  5671   attrMap: {
  5672.  5672   "class": "className",
  5673.  5673   "for": "htmlFor"
  5674.  5674   },
  5675.  5675   attrHandle: {
  5676.  5676   href: function(elem){
  5677.  5677   return elem.getAttribute("href");
  5678.  5678   }
  5679.  5679   },
  5680.  5680   relative: {
  5681.  5681   "+": function(checkSet, part){
  5682.  5682   var isPartStr = typeof part === "string",
  5683.  5683   isTag = isPartStr && !/\W/.test(part),
  5684.  5684   isPartStrNotTag = isPartStr && !isTag;
  5685.  5685  
  5686.  5686   if ( isTag ) {
  5687.  5687   part = part.toLowerCase();
  5688.  5688   }
  5689.  5689  
  5690.  5690   for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) {
  5691.  5691   if ( (elem = checkSet[i]) ) {
  5692.  5692   while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {}
  5693.  5693  
  5694.  5694   checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ?
  5695.  5695   elem || false :
  5696.  5696   elem === part;
  5697.  5697   }
  5698.  5698   }
  5699.  5699  
  5700.  5700   if ( isPartStrNotTag ) {
  5701.  5701   Sizzle.filter( part, checkSet, true );
  5702.  5702   }
  5703.  5703   },
  5704.  5704   ">": function(checkSet, part){
  5705.  5705   var isPartStr = typeof part === "string",
  5706.  5706   elem, i = 0, l = checkSet.length;
  5707.  5707  
  5708.  5708   if ( isPartStr && !/\W/.test(part) ) {
  5709.  5709   part = part.toLowerCase();
  5710.  5710  
  5711.  5711   for ( ; i < l; i++ ) {
  5712.  5712   elem = checkSet[i];
  5713.  5713   if ( elem ) {
  5714.  5714   var parent = elem.parentNode;
  5715.  5715   checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false;
  5716.  5716   }
  5717.  5717   }
  5718.  5718   } else {
  5719.  5719   for ( ; i < l; i++ ) {
  5720.  5720   elem = checkSet[i];
  5721.  5721   if ( elem ) {
  5722.  5722   checkSet[i] = isPartStr ?
  5723.  5723   elem.parentNode :
  5724.  5724   elem.parentNode === part;
  5725.  5725   }
  5726.  5726   }
  5727.  5727  
  5728.  5728   if ( isPartStr ) {
  5729.  5729   Sizzle.filter( part, checkSet, true );
  5730.  5730   }
  5731.  5731   }
  5732.  5732   },
  5733.  5733   "": function(checkSet, part, isXML){
  5734.  5734   var doneName = done++, checkFn = dirCheck, nodeCheck;
  5735.  5735  
  5736.  5736   if ( typeof part === "string" && !/\W/.test(part) ) {
  5737.  5737   part = part.toLowerCase();
  5738.  5738   nodeCheck = part;
  5739.  5739   checkFn = dirNodeCheck;
  5740.  5740   }
  5741.  5741  
  5742.  5742   checkFn("parentNode", part, doneName, checkSet, nodeCheck, isXML);
  5743.  5743   },
  5744.  5744   "~": function(checkSet, part, isXML){
  5745.  5745   var doneName = done++, checkFn = dirCheck, nodeCheck;
  5746.  5746  
  5747.  5747   if ( typeof part === "string" && !/\W/.test(part) ) {
  5748.  5748   part = part.toLowerCase();
  5749.  5749   nodeCheck = part;
  5750.  5750   checkFn = dirNodeCheck;
  5751.  5751   }
  5752.  5752  
  5753.  5753   checkFn("previousSibling", part, doneName, checkSet, nodeCheck, isXML);
  5754.  5754   }
  5755.  5755   },
  5756.  5756   find: {
  5757.  5757   ID: function(match, context, isXML){
  5758.  5758   if ( typeof context.getElementById !== "undefined" && !isXML ) {
  5759.  5759   var m = context.getElementById(match[1]);
  5760.  5760   return m ? [m] : [];
  5761.  5761   }
  5762.  5762   },
  5763.  5763   NAME: function(match, context){
  5764.  5764   if ( typeof context.getElementsByName !== "undefined" ) {
  5765.  5765   var ret = [], results = context.getElementsByName(match[1]);
  5766.  5766  
  5767.  5767   for ( var i = 0, l = results.length; i < l; i++ ) {
  5768.  5768   if ( results[i].getAttribute("name") === match[1] ) {
  5769.  5769   ret.push( results[i] );
  5770.  5770   }
  5771.  5771   }
  5772.  5772  
  5773.  5773   return ret.length === 0 ? null : ret;
  5774.  5774   }
  5775.  5775   },
  5776.  5776   TAG: function(match, context){
  5777.  5777   return context.getElementsByTagName(match[1]);
  5778.  5778   }
  5779.  5779   },
  5780.  5780   preFilter: {
  5781.  5781   CLASS: function(match, curLoop, inplace, result, not, isXML){
  5782.  5782   match = " " + match[1].replace(/\\/g, "") + " ";
  5783.  5783  
  5784.  5784   if ( isXML ) {
  5785.  5785   return match;
  5786.  5786   }
  5787.  5787  
  5788.  5788   for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) {
  5789.  5789   if ( elem ) {
  5790.  5790   if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n]/g, " ").indexOf(match) >= 0) ) {
  5791.  5791   if ( !inplace ) {
  5792.  5792   result.push( elem );
  5793.  5793   }
  5794.  5794   } else if ( inplace ) {
  5795.  5795   curLoop[i] = false;
  5796.  5796   }
  5797.  5797   }
  5798.  5798   }
  5799.  5799  
  5800.  5800   return false;
  5801.  5801   },
  5802.  5802   ID: function(match){
  5803.  5803   return match[1].replace(/\\/g, "");
  5804.  5804   },
  5805.  5805   TAG: function(match, curLoop){
  5806.  5806   return match[1].toLowerCase();
  5807.  5807   },
  5808.  5808   CHILD: function(match){
  5809.  5809   if ( match[1] === "nth" ) {
  5810.  5810   // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6'
  5811.  5811   var test = /(-?)(\d*)n((?:\+|-)?\d*)/.exec(
  5812.  5812   match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" ||
  5813.  5813   !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]);
  5814.  5814  
  5815.  5815   // calculate the numbers (first)n+(last) including if they are negative
  5816.  5816   match[2] = (test[1] + (test[2] || 1)) - 0;
  5817.  5817   match[3] = test[3] - 0;
  5818.  5818   }
  5819.  5819  
  5820.  5820   // TODO: Move to normal caching system
  5821.  5821   match[0] = done++;
  5822.  5822  
  5823.  5823   return match;
  5824.  5824   },
  5825.  5825   ATTR: function(match, curLoop, inplace, result, not, isXML){
  5826.  5826   var name = match[1].replace(/\\/g, "");
  5827.  5827  
  5828.  5828   if ( !isXML && Expr.attrMap[name] ) {
  5829.  5829   match[1] = Expr.attrMap[name];
  5830.  5830   }
  5831.  5831  
  5832.  5832   if ( match[2] === "~=" ) {
  5833.  5833   match[4] = " " + match[4] + " ";
  5834.  5834   }
  5835.  5835  
  5836.  5836   return match;
  5837.  5837   },
  5838.  5838   PSEUDO: function(match, curLoop, inplace, result, not){
  5839.  5839   if ( match[1] === "not" ) {
  5840.  5840   // If we're dealing with a complex expression, or a simple one
  5841.  5841   if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) {
  5842.  5842   match[3] = Sizzle(match[3], null, null, curLoop);
  5843.  5843   } else {
  5844.  5844   var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not);
  5845.  5845   if ( !inplace ) {
  5846.  5846   result.push.apply( result, ret );
  5847.  5847   }
  5848.  5848   return false;
  5849.  5849   }
  5850.  5850   } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) {
  5851.  5851   return true;
  5852.  5852   }
  5853.  5853  
  5854.  5854   return match;
  5855.  5855   },
  5856.  5856   POS: function(match){
  5857.  5857   match.unshift( true );
  5858.  5858   return match;
  5859.  5859   }
  5860.  5860   },
  5861.  5861   filters: {
  5862.  5862   enabled: function(elem){
  5863.  5863   return elem.disabled === false && elem.type !== "hidden";
  5864.  5864   },
  5865.  5865   disabled: function(elem){
  5866.  5866   return elem.disabled === true;
  5867.  5867   },
  5868.  5868   checked: function(elem){
  5869.  5869   return elem.checked === true;
  5870.  5870   },
  5871.  5871   selected: function(elem){
  5872.  5872   // Accessing this property makes selected-by-default
  5873.  5873   // options in Safari work properly
  5874.  5874   elem.parentNode.selectedIndex;
  5875.  5875   return elem.selected === true;
  5876.  5876   },
  5877.  5877   parent: function(elem){
  5878.  5878   return !!elem.firstChild;
  5879.  5879   },
  5880.  5880   empty: function(elem){
  5881.  5881   return !elem.firstChild;
  5882.  5882   },
  5883.  5883   has: function(elem, i, match){
  5884.  5884   return !!Sizzle( match[3], elem ).length;
  5885.  5885   },
  5886.  5886   header: function(elem){
  5887.  5887   return (/h\d/i).test( elem.nodeName );
  5888.  5888   },
  5889.  5889   text: function(elem){
  5890.  5890   return "text" === elem.type;
  5891.  5891   },
  5892.  5892   radio: function(elem){
  5893.  5893   return "radio" === elem.type;
  5894.  5894   },
  5895.  5895   checkbox: function(elem){
  5896.  5896   return "checkbox" === elem.type;
  5897.  5897   },
  5898.  5898   file: function(elem){
  5899.  5899   return "file" === elem.type;
  5900.  5900   },
  5901.  5901   password: function(elem){
  5902.  5902   return "password" === elem.type;
  5903.  5903   },
  5904.  5904   submit: function(elem){
  5905.  5905   return "submit" === elem.type;
  5906.  5906   },
  5907.  5907   image: function(elem){
  5908.  5908   return "image" === elem.type;
  5909.  5909   },
  5910.  5910   reset: function(elem){
  5911.  5911   return "reset" === elem.type;
  5912.  5912   },
  5913.  5913   button: function(elem){
  5914.  5914   return "button" === elem.type || elem.nodeName.toLowerCase() === "button";
  5915.  5915   },
  5916.  5916   input: function(elem){
  5917.  5917   return (/input|select|textarea|button/i).test(elem.nodeName);
  5918.  5918   }
  5919.  5919   },
  5920.  5920   setFilters: {
  5921.  5921   first: function(elem, i){
  5922.  5922   return i === 0;
  5923.  5923   },
  5924.  5924   last: function(elem, i, match, array){
  5925.  5925   return i === array.length - 1;
  5926.  5926   },
  5927.  5927   even: function(elem, i){
  5928.  5928   return i % 2 === 0;
  5929.  5929   },
  5930.  5930   odd: function(elem, i){
  5931.  5931   return i % 2 === 1;
  5932.  5932   },
  5933.  5933   lt: function(elem, i, match){
  5934.  5934   return i < match[3] - 0;
  5935.  5935   },
  5936.  5936   gt: function(elem, i, match){
  5937.  5937   return i > match[3] - 0;
  5938.  5938   },
  5939.  5939   nth: function(elem, i, match){
  5940.  5940   return match[3] - 0 === i;
  5941.  5941   },
  5942.  5942   eq: function(elem, i, match){
  5943.  5943   return match[3] - 0 === i;
  5944.  5944   }
  5945.  5945   },
  5946.  5946   filter: {
  5947.  5947   PSEUDO: function(elem, match, i, array){
  5948.  5948   var name = match[1], filter = Expr.filters[ name ];
  5949.  5949  
  5950.  5950   if ( filter ) {
  5951.  5951   return filter( elem, i, match, array );
  5952.  5952   } else if ( name === "contains" ) {
  5953.  5953   return (elem.textContent || elem.innerText || Sizzle.getText([ elem ]) || "").indexOf(match[3]) >= 0;
  5954.  5954   } else if ( name === "not" ) {
  5955.  5955   var not = match[3];
  5956.  5956  
  5957.  5957   for ( var j = 0, l = not.length; j < l; j++ ) {
  5958.  5958   if ( not[j] === elem ) {
  5959.  5959   return false;
  5960.  5960   }
  5961.  5961   }
  5962.  5962  
  5963.  5963   return true;
  5964.  5964   } else {
  5965.  5965   Sizzle.error( "Syntax error, unrecognized expression: " + name );
  5966.  5966   }
  5967.  5967   },
  5968.  5968   CHILD: function(elem, match){
  5969.  5969   var type = match[1], node = elem;
  5970.  5970   switch (type) {
  5971.  5971   case 'only':
  5972.  5972   case 'first':
  5973.  5973   while ( (node = node.previousSibling) ) {
  5974.  5974   if ( node.nodeType === 1 ) {
  5975.  5975   return false;
  5976.  5976   }
  5977.  5977   }
  5978.  5978   if ( type === "first" ) {
  5979.  5979   return true;
  5980.  5980   }
  5981.  5981   node = elem;
  5982.  5982   case 'last':
  5983.  5983   while ( (node = node.nextSibling) ) {
  5984.  5984   if ( node.nodeType === 1 ) {
  5985.  5985   return false;
  5986.  5986   }
  5987.  5987   }
  5988.  5988   return true;
  5989.  5989   case 'nth':
  5990.  5990   var first = match[2], last = match[3];
  5991.  5991  
  5992.  5992   if ( first === 1 && last === 0 ) {
  5993.  5993   return true;
  5994.  5994   }
  5995.  5995  
  5996.  5996   var doneName = match[0],
  5997.  5997   parent = elem.parentNode;
  5998.  5998  
  5999.  5999   if ( parent && (parent.sizcache !== doneName || !elem.nodeIndex) ) {
  6000.  6000   var count = 0;
  6001.  6001   for ( node = parent.firstChild; node; node = node.nextSibling ) {
  6002.  6002   if ( node.nodeType === 1 ) {
  6003.  6003   node.nodeIndex = ++count;
  6004.  6004   }
  6005.  6005   }
  6006.  6006   parent.sizcache = doneName;
  6007.  6007   }
  6008.  6008  
  6009.  6009   var diff = elem.nodeIndex - last;
  6010.  6010   if ( first === 0 ) {
  6011.  6011   return diff === 0;
  6012.  6012   } else {
  6013.  6013   return ( diff % first === 0 && diff / first >= 0 );
  6014.  6014   }
  6015.  6015   }
  6016.  6016   },
  6017.  6017   ID: function(elem, match){
  6018.  6018   return elem.nodeType === 1 && elem.getAttribute("id") === match;
  6019.  6019   },
  6020.  6020   TAG: function(elem, match){
  6021.  6021   return (match === "*" && elem.nodeType === 1) || elem.nodeName.toLowerCase() === match;
  6022.  6022   },
  6023.  6023   CLASS: function(elem, match){
  6024.  6024   return (" " + (elem.className || elem.getAttribute("class")) + " ")
  6025.  6025   .indexOf( match ) > -1;
  6026.  6026   },
  6027.  6027   ATTR: function(elem, match){
  6028.  6028   var name = match[1],
  6029.  6029   result = Expr.attrHandle[ name ] ?
  6030.  6030   Expr.attrHandle[ name ]( elem ) :
  6031.  6031   elem[ name ] != null ?
  6032.  6032   elem[ name ] :
  6033.  6033   elem.getAttribute( name ),
  6034.  6034   value = result + "",
  6035.  6035   type = match[2],
  6036.  6036   check = match[4];
  6037.  6037  
  6038.  6038   return result == null ?
  6039.  6039   type === "!=" :
  6040.  6040   type === "=" ?
  6041.  6041   value === check :
  6042.  6042   type === "*=" ?
  6043.  6043   value.indexOf(check) >= 0 :
  6044.  6044   type === "~=" ?
  6045.  6045   (" " + value + " ").indexOf(check) >= 0 :
  6046.  6046   !check ?
  6047.  6047   value && result !== false :
  6048.  6048   type === "!=" ?
  6049.  6049   value !== check :
  6050.  6050   type === "^=" ?
  6051.  6051   value.indexOf(check) === 0 :
  6052.  6052   type === "$=" ?
  6053.  6053   value.substr(value.length - check.length) === check :
  6054.  6054   type === "|=" ?
  6055.  6055   value === check || value.substr(0, check.length + 1) === check + "-" :
  6056.  6056   false;
  6057.  6057   },
  6058.  6058   POS: function(elem, match, i, array){
  6059.  6059   var name = match[2], filter = Expr.setFilters[ name ];
  6060.  6060  
  6061.  6061   if ( filter ) {
  6062.  6062   return filter( elem, i, match, array );
  6063.  6063   }
  6064.  6064   }
  6065.  6065   }
  6066.  6066  };
  6067.  6067  
  6068.  6068  var origPOS = Expr.match.POS,
  6069.  6069   fescape = function(all, num){
  6070.  6070   return "\\" + (num - 0 + 1);
  6071.  6071   };
  6072.  6072  
  6073.  6073  for ( var type in Expr.match ) {
  6074.  6074   Expr.match[ type ] = new RegExp( Expr.match[ type ].source + (/(?![^\[]*\])(?![^\(]*\))/.source) );
  6075.  6075   Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, fescape) );
  6076.  6076  }
  6077.  6077  
  6078.  6078  var makeArray = function(array, results) {
  6079.  6079   array = Array.prototype.slice.call( array, 0 );
  6080.  6080  
  6081.  6081   if ( results ) {
  6082.  6082   results.push.apply( results, array );
  6083.  6083   return results;
  6084.  6084   }
  6085.  6085  
  6086.  6086   return array;
  6087.  6087  };
  6088.  6088  
  6089.  6089  // Perform a simple check to determine if the browser is capable of
  6090.  6090  // converting a NodeList to an array using builtin methods.
  6091.  6091  // Also verifies that the returned array holds DOM nodes
  6092.  6092  // (which is not the case in the Blackberry browser)
  6093.  6093  try {
  6094.  6094   Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType;
  6095.  6095  
  6096.  6096  // Provide a fallback method if it does not work
  6097.  6097  } catch(e){
  6098.  6098   makeArray = function(array, results) {
  6099.  6099   var ret = results || [], i = 0;
  6100.  6100  
  6101.  6101   if ( toString.call(array) === "[object Array]" ) {
  6102.  6102   Array.prototype.push.apply( ret, array );
  6103.  6103   } else {
  6104.  6104   if ( typeof array.length === "number" ) {
  6105.  6105   for ( var l = array.length; i < l; i++ ) {
  6106.  6106   ret.push( array[i] );
  6107.  6107   }
  6108.  6108   } else {
  6109.  6109   for ( ; array[i]; i++ ) {
  6110.  6110   ret.push( array[i] );
  6111.  6111   }
  6112.  6112   }
  6113.  6113   }
  6114.  6114  
  6115.  6115   return ret;
  6116.  6116   };
  6117.  6117  }
  6118.  6118  
  6119.  6119  var sortOrder;
  6120.  6120  
  6121.  6121  if ( document.documentElement.compareDocumentPosition ) {
  6122.  6122   sortOrder = function( a, b ) {
  6123.  6123   if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) {
  6124.  6124   if ( a == b ) {
  6125.  6125   hasDuplicate = true;
  6126.  6126   }
  6127.  6127   return a.compareDocumentPosition ? -1 : 1;
  6128.  6128   }
  6129.  6129  
  6130.  6130   var ret = a.compareDocumentPosition(b) & 4 ? -1 : a === b ? 0 : 1;
  6131.  6131   if ( ret === 0 ) {
  6132.  6132   hasDuplicate = true;
  6133.  6133   }
  6134.  6134   return ret;
  6135.  6135   };
  6136.  6136  } else if ( "sourceIndex" in document.documentElement ) {
  6137.  6137   sortOrder = function( a, b ) {
  6138.  6138   if ( !a.sourceIndex || !b.sourceIndex ) {
  6139.  6139   if ( a == b ) {
  6140.  6140   hasDuplicate = true;
  6141.  6141   }
  6142.  6142   return a.sourceIndex ? -1 : 1;
  6143.  6143   }
  6144.  6144  
  6145.  6145   var ret = a.sourceIndex - b.sourceIndex;
  6146.  6146   if ( ret === 0 ) {
  6147.  6147   hasDuplicate = true;
  6148.  6148   }
  6149.  6149   return ret;
  6150.  6150   };
  6151.  6151  } else if ( document.createRange ) {
  6152.  6152   sortOrder = function( a, b ) {
  6153.  6153   if ( !a.ownerDocument || !b.ownerDocument ) {
  6154.  6154   if ( a == b ) {
  6155.  6155   hasDuplicate = true;
  6156.  6156   }
  6157.  6157   return a.ownerDocument ? -1 : 1;
  6158.  6158   }
  6159.  6159  
  6160.  6160   var aRange = a.ownerDocument.createRange(), bRange = b.ownerDocument.createRange();
  6161.  6161   aRange.setStart(a, 0);
  6162.  6162   aRange.setEnd(a, 0);
  6163.  6163   bRange.setStart(b, 0);
  6164.  6164   bRange.setEnd(b, 0);
  6165.  6165   var ret = aRange.compareBoundaryPoints(Range.START_TO_END, bRange);
  6166.  6166   if ( ret === 0 ) {
  6167.  6167   hasDuplicate = true;
  6168.  6168   }
  6169.  6169   return ret;
  6170.  6170   };
  6171.  6171  }
  6172.  6172  
  6173.  6173  // Utility function for retreiving the text value of an array of DOM nodes
  6174.  6174  Sizzle.getText = function( elems ) {
  6175.  6175   var ret = "", elem;
  6176.  6176  
  6177.  6177   for ( var i = 0; elems[i]; i++ ) {
  6178.  6178   elem = elems[i];
  6179.  6179  
  6180.  6180   // Get the text from text nodes and CDATA nodes
  6181.  6181   if ( elem.nodeType === 3 || elem.nodeType === 4 ) {
  6182.  6182   ret += elem.nodeValue;
  6183.  6183  
  6184.  6184   // Traverse everything else, except comment nodes
  6185.  6185   } else if ( elem.nodeType !== 8 ) {
  6186.  6186   ret += Sizzle.getText( elem.childNodes );
  6187.  6187   }
  6188.  6188   }
  6189.  6189  
  6190.  6190   return ret;
  6191.  6191  };
  6192.  6192  
  6193.  6193  // Check to see if the browser returns elements by name when
  6194.  6194  // querying by getElementById (and provide a workaround)
  6195.  6195  (function(){
  6196.  6196   // We're going to inject a fake input element with a specified name
  6197.  6197   var form = document.createElement("div"),
  6198.  6198   id = "script" + (new Date()).getTime();
  6199.  6199   form.innerHTML = "<a name='" + id + "'/>";
  6200.  6200  
  6201.  6201   // Inject it into the root element, check its status, and remove it quickly
  6202.  6202   var root = document.documentElement;
  6203.  6203   root.insertBefore( form, root.firstChild );
  6204.  6204  
  6205.  6205   // The workaround has to do additional checks after a getElementById
  6206.  6206   // Which slows things down for other browsers (hence the branching)
  6207.  6207   if ( document.getElementById( id ) ) {
  6208.  6208   Expr.find.ID = function(match, context, isXML){
  6209.  6209   if ( typeof context.getElementById !== "undefined" && !isXML ) {
  6210.  6210   var m = context.getElementById(match[1]);
  6211.  6211   return m ? m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? [m] : undefined : [];
  6212.  6212   }
  6213.  6213   };
  6214.  6214  
  6215.  6215   Expr.filter.ID = function(elem, match){
  6216.  6216   var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id");
  6217.  6217   return elem.nodeType === 1 && node && node.nodeValue === match;
  6218.  6218   };
  6219.  6219   }
  6220.  6220  
  6221.  6221   root.removeChild( form );
  6222.  6222   root = form = null; // release memory in IE
  6223.  6223  })();
  6224.  6224  
  6225.  6225  (function(){
  6226.  6226   // Check to see if the browser returns only elements
  6227.  6227   // when doing getElementsByTagName("*")
  6228.  6228  
  6229.  6229   // Create a fake element
  6230.  6230   var div = document.createElement("div");
  6231.  6231   div.appendChild( document.createComment("") );
  6232.  6232  
  6233.  6233   // Make sure no comments are found
  6234.  6234   if ( div.getElementsByTagName("*").length > 0 ) {
  6235.  6235   Expr.find.TAG = function(match, context){
  6236.  6236   var results = context.getElementsByTagName(match[1]);
  6237.  6237  
  6238.  6238   // Filter out possible comments
  6239.  6239   if ( match[1] === "*" ) {
  6240.  6240   var tmp = [];
  6241.  6241  
  6242.  6242   for ( var i = 0; results[i]; i++ ) {
  6243.  6243   if ( results[i].nodeType === 1 ) {
  6244.  6244   tmp.push( results[i] );
  6245.  6245   }
  6246.  6246   }
  6247.  6247  
  6248.  6248   results = tmp;
  6249.  6249   }
  6250.  6250  
  6251.  6251   return results;
  6252.  6252   };
  6253.  6253   }
  6254.  6254  
  6255.  6255   // Check to see if an attribute returns normalized href attributes
  6256.  6256   div.innerHTML = "<a href='#'></a>";
  6257.  6257   if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" &&
  6258.  6258   div.firstChild.getAttribute("href") !== "#" ) {
  6259.  6259   Expr.attrHandle.href = function(elem){
  6260.  6260   return elem.getAttribute("href", 2);
  6261.  6261   };
  6262.  6262   }
  6263.  6263  
  6264.  6264   div = null; // release memory in IE
  6265.  6265  })();
  6266.  6266  
  6267.  6267  if ( document.querySelectorAll ) {
  6268.  6268   (function(){
  6269.  6269   var oldSizzle = Sizzle, div = document.createElement("div");
  6270.  6270   div.innerHTML = "<p class='TEST'></p>";
  6271.  6271  
  6272.  6272   // Safari can't handle uppercase or unicode characters when
  6273.  6273   // in quirks mode.
  6274.  6274   if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) {
  6275.  6275   return;
  6276.  6276   }
  6277.  6277  
  6278.  6278   Sizzle = function(query, context, extra, seed){
  6279.  6279   context = context || document;
  6280.  6280  
  6281.  6281   // Only use querySelectorAll on non-XML documents
  6282.  6282   // (ID selectors don't work in non-HTML documents)
  6283.  6283   if ( !seed && context.nodeType === 9 && !Sizzle.isXML(context) ) {
  6284.  6284   try {
  6285.  6285   return makeArray( context.querySelectorAll(query), extra );
  6286.  6286   } catch(e){}
  6287.  6287   }
  6288.  6288  
  6289.  6289   return oldSizzle(query, context, extra, seed);
  6290.  6290   };
  6291.  6291  
  6292.  6292   for ( var prop in oldSizzle ) {
  6293.  6293   Sizzle[ prop ] = oldSizzle[ prop ];
  6294.  6294   }
  6295.  6295  
  6296.  6296   div = null; // release memory in IE
  6297.  6297   })();
  6298.  6298  }
  6299.  6299  
  6300.  6300  (function(){
  6301.  6301   var div = document.createElement("div");
  6302.  6302  
  6303.  6303   div.innerHTML = "<div class='test e'></div><div class='test'></div>";
  6304.  6304  
  6305.  6305   // Opera can't find a second classname (in 9.6)
  6306.  6306   // Also, make sure that getElementsByClassName actually exists
  6307.  6307   if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) {
  6308.  6308   return;
  6309.  6309   }
  6310.  6310  
  6311.  6311   // Safari caches class attributes, doesn't catch changes (in 3.2)
  6312.  6312   div.lastChild.className = "e";
  6313.  6313  
  6314.  6314   if ( div.getElementsByClassName("e").length === 1 ) {
  6315.  6315   return;
  6316.  6316   }
  6317.  6317  
  6318.  6318   Expr.order.splice(1, 0, "CLASS");
  6319.  6319   Expr.find.CLASS = function(match, context, isXML) {
  6320.  6320   if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) {
  6321.  6321   return context.getElementsByClassName(match[1]);
  6322.  6322   }
  6323.  6323   };
  6324.  6324  
  6325.  6325   div = null; // release memory in IE
  6326.  6326  })();
  6327.  6327  
  6328.  6328  function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
  6329.  6329   for ( var i = 0, l = checkSet.length; i < l; i++ ) {
  6330.  6330   var elem = checkSet[i];
  6331.  6331   if ( elem ) {
  6332.  6332   elem = elem[dir];
  6333.  6333   var match = false;
  6334.  6334  
  6335.  6335   while ( elem ) {
  6336.  6336   if ( elem.sizcache === doneName ) {
  6337.  6337   match = checkSet[elem.sizset];
  6338.  6338   break;
  6339.  6339   }
  6340.  6340  
  6341.  6341   if ( elem.nodeType === 1 && !isXML ){
  6342.  6342   elem.sizcache = doneName;
  6343.  6343   elem.sizset = i;
  6344.  6344   }
  6345.  6345  
  6346.  6346   if ( elem.nodeName.toLowerCase() === cur ) {
  6347.  6347   match = elem;
  6348.  6348   break;
  6349.  6349   }
  6350.  6350  
  6351.  6351   elem = elem[dir];
  6352.  6352   }
  6353.  6353  
  6354.  6354   checkSet[i] = match;
  6355.  6355   }
  6356.  6356   }
  6357.  6357  }
  6358.  6358  
  6359.  6359  function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
  6360.  6360   for ( var i = 0, l = checkSet.length; i < l; i++ ) {
  6361.  6361   var elem = checkSet[i];
  6362.  6362   if ( elem ) {
  6363.  6363   elem = elem[dir];
  6364.  6364   var match = false;
  6365.  6365  
  6366.  6366   while ( elem ) {
  6367.  6367   if ( elem.sizcache === doneName ) {
  6368.  6368   match = checkSet[elem.sizset];
  6369.  6369   break;
  6370.  6370   }
  6371.  6371  
  6372.  6372   if ( elem.nodeType === 1 ) {
  6373.  6373   if ( !isXML ) {
  6374.  6374   elem.sizcache = doneName;
  6375.  6375   elem.sizset = i;
  6376.  6376   }
  6377.  6377   if ( typeof cur !== "string" ) {
  6378.  6378   if ( elem === cur ) {
  6379.  6379   match = true;
  6380.  6380   break;
  6381.  6381   }
  6382.  6382  
  6383.  6383   } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) {
  6384.  6384   match = elem;
  6385.  6385   break;
  6386.  6386   }
  6387.  6387   }
  6388.  6388  
  6389.  6389   elem = elem[dir];
  6390.  6390   }
  6391.  6391  
  6392.  6392   checkSet[i] = match;
  6393.  6393   }
  6394.  6394   }
  6395.  6395  }
  6396.  6396  
  6397.  6397  Sizzle.contains = document.compareDocumentPosition ? function(a, b){
  6398.  6398   return !!(a.compareDocumentPosition(b) & 16);
  6399.  6399  } : function(a, b){
  6400.  6400   return a !== b && (a.contains ? a.contains(b) : true);
  6401.  6401  };
  6402.  6402  
  6403.  6403  Sizzle.isXML = function(elem){
  6404.  6404   // documentElement is verified for cases where it doesn't yet exist
  6405.  6405   // (such as loading iframes in IE - #4833)
  6406.  6406   var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement;
  6407.  6407   return documentElement ? documentElement.nodeName !== "HTML" : false;
  6408.  6408  };
  6409.  6409  
  6410.  6410  var posProcess = function(selector, context){
  6411.  6411   var tmpSet = [], later = "", match,
  6412.  6412   root = context.nodeType ? [context] : context;
  6413.  6413  
  6414.  6414   // Position selectors must be done after the filter
  6415.  6415   // And so must :not(positional) so we move all PSEUDOs to the end
  6416.  6416   while ( (match = Expr.match.PSEUDO.exec( selector )) ) {
  6417.  6417   later += match[0];
  6418.  6418   selector = selector.replace( Expr.match.PSEUDO, "" );
  6419.  6419   }
  6420.  6420  
  6421.  6421   selector = Expr.relative[selector] ? selector + "*" : selector;
  6422.  6422  
  6423.  6423   for ( var i = 0, l = root.length; i < l; i++ ) {
  6424.  6424   Sizzle( selector, root[i], tmpSet );
  6425.  6425   }
  6426.  6426  
  6427.  6427   return Sizzle.filter( later, tmpSet );
  6428.  6428  };
  6429.  6429  
  6430.  6430  // EXPOSE
  6431.  6431  
  6432.  6432  window.tinymce.dom.Sizzle = Sizzle;
  6433.  6433  
  6434.  6434  })();
  6435.  6435  
  6436.  6436  
  6437.  6437  (function(tinymce) {
  6438.  6438   // Shorten names
  6439.  6439   var each = tinymce.each, DOM = tinymce.DOM, isIE = tinymce.isIE, isWebKit = tinymce.isWebKit, Event;
  6440.  6440  
  6441.  6441   tinymce.create('tinymce.dom.EventUtils', {
  6442.  6442   EventUtils : function() {
  6443.  6443   this.inits = [];
  6444.  6444   this.events = [];
  6445.  6445   },
  6446.  6446  
  6447.  6447   add : function(o, n, f, s) {
  6448.  6448   var cb, t = this, el = t.events, r;
  6449.  6449  
  6450.  6450   if (n instanceof Array) {
  6451.  6451   r = [];
  6452.  6452  
  6453.  6453   each(n, function(n) {
  6454.  6454   r.push(t.add(o, n, f, s));
  6455.  6455   });
  6456.  6456  
  6457.  6457   return r;
  6458.  6458   }
  6459.  6459  
  6460.  6460   // Handle array
  6461.  6461   if (o && o.hasOwnProperty && o instanceof Array) {
  6462.  6462   r = [];
  6463.  6463  
  6464.  6464   each(o, function(o) {
  6465.  6465   o = DOM.get(o);
  6466.  6466   r.push(t.add(o, n, f, s));
  6467.  6467   });
  6468.  6468  
  6469.  6469   return r;
  6470.  6470   }
  6471.  6471  
  6472.  6472   o = DOM.get(o);
  6473.  6473  
  6474.  6474   if (!o)
  6475.  6475   return;
  6476.  6476  
  6477.  6477   // Setup event callback
  6478.  6478   cb = function(e) {
  6479.  6479   // Is all events disabled
  6480.  6480   if (t.disabled)
  6481.  6481   return;
  6482.  6482  
  6483.  6483   e = e || window.event;
  6484.  6484  
  6485.  6485   // Patch in target, preventDefault and stopPropagation in IE it's W3C valid
  6486.  6486   if (e && isIE) {
  6487.  6487   if (!e.target)
  6488.  6488   e.target = e.srcElement;
  6489.  6489  
  6490.  6490   // Patch in preventDefault, stopPropagation methods for W3C compatibility
  6491.  6491   tinymce.extend(e, t._stoppers);
  6492.  6492   }
  6493.  6493  
  6494.  6494   if (!s)
  6495.  6495   return f(e);
  6496.  6496  
  6497.  6497   return f.call(s, e);
  6498.  6498   };
  6499.  6499  
  6500.  6500   if (n == 'unload') {
  6501.  6501   tinymce.unloads.unshift({func : cb});
  6502.  6502   return cb;
  6503.  6503   }
  6504.  6504  
  6505.  6505   if (n == 'init') {
  6506.  6506   if (t.domLoaded)
  6507.  6507   cb();
  6508.  6508   else
  6509.  6509   t.inits.push(cb);
  6510.  6510  
  6511.  6511   return cb;
  6512.  6512   }
  6513.  6513  
  6514.  6514   // Store away listener reference
  6515.  6515   el.push({
  6516.  6516   obj : o,
  6517.  6517   name : n,
  6518.  6518   func : f,
  6519.  6519   cfunc : cb,
  6520.  6520   scope : s
  6521.  6521   });
  6522.  6522  
  6523.  6523   t._add(o, n, cb);
  6524.  6524  
  6525.  6525   return f;
  6526.  6526   },
  6527.  6527  
  6528.  6528   remove : function(o, n, f) {
  6529.  6529   var t = this, a = t.events, s = false, r;
  6530.  6530  
  6531.  6531   // Handle array
  6532.  6532   if (o && o.hasOwnProperty && o instanceof Array) {
  6533.  6533   r = [];
  6534.  6534  
  6535.  6535   each(o, function(o) {
  6536.  6536   o = DOM.get(o);
  6537.  6537   r.push(t.remove(o, n, f));
  6538.  6538   });
  6539.  6539  
  6540.  6540   return r;
  6541.  6541   }
  6542.  6542  
  6543.  6543   o = DOM.get(o);
  6544.  6544  
  6545.  6545   each(a, function(e, i) {
  6546.  6546   if (e.obj == o && e.name == n && (!f || (e.func == f || e.cfunc == f))) {
  6547.  6547   a.splice(i, 1);
  6548.  6548   t._remove(o, n, e.cfunc);
  6549.  6549   s = true;
  6550.  6550   return false;
  6551.  6551   }
  6552.  6552   });
  6553.  6553  
  6554.  6554   return s;
  6555.  6555   },
  6556.  6556  
  6557.  6557   clear : function(o) {
  6558.  6558   var t = this, a = t.events, i, e;
  6559.  6559  
  6560.  6560   if (o) {
  6561.  6561   o = DOM.get(o);
  6562.  6562  
  6563.  6563   for (i = a.length - 1; i >= 0; i--) {
  6564.  6564   e = a[i];
  6565.  6565  
  6566.  6566   if (e.obj === o) {
  6567.  6567   t._remove(e.obj, e.name, e.cfunc);
  6568.  6568   e.obj = e.cfunc = null;
  6569.  6569   a.splice(i, 1);
  6570.  6570   }
  6571.  6571   }
  6572.  6572   }
  6573.  6573   },
  6574.  6574  
  6575.  6575   cancel : function(e) {
  6576.  6576   if (!e)
  6577.  6577   return false;
  6578.  6578  
  6579.  6579   this.stop(e);
  6580.  6580  
  6581.  6581   return this.prevent(e);
  6582.  6582   },
  6583.  6583  
  6584.  6584   stop : function(e) {
  6585.  6585   if (e.stopPropagation)
  6586.  6586   e.stopPropagation();
  6587.  6587   else
  6588.  6588   e.cancelBubble = true;
  6589.  6589  
  6590.  6590   return false;
  6591.  6591   },
  6592.  6592  
  6593.  6593   prevent : function(e) {
  6594.  6594   if (e.preventDefault)
  6595.  6595   e.preventDefault();
  6596.  6596   else
  6597.  6597   e.returnValue = false;
  6598.  6598  
  6599.  6599   return false;
  6600.  6600   },
  6601.  6601  
  6602.  6602   destroy : function() {
  6603.  6603   var t = this;
  6604.  6604  
  6605.  6605   each(t.events, function(e, i) {
  6606.  6606   t._remove(e.obj, e.name, e.cfunc);
  6607.  6607   e.obj = e.cfunc = null;
  6608.  6608   });
  6609.  6609  
  6610.  6610   t.events = [];
  6611.  6611   t = null;
  6612.  6612   },
  6613.  6613  
  6614.  6614   _add : function(o, n, f) {
  6615.  6615   if (o.attachEvent)
  6616.  6616   o.attachEvent('on' + n, f);
  6617.  6617   else if (o.addEventListener)
  6618.  6618   o.addEventListener(n, f, false);
  6619.  6619   else
  6620.  6620   o['on' + n] = f;
  6621.  6621   },
  6622.  6622  
  6623.  6623   _remove : function(o, n, f) {
  6624.  6624   if (o) {
  6625.  6625   try {
  6626.  6626   if (o.detachEvent)
  6627.  6627   o.detachEvent('on' + n, f);
  6628.  6628   else if (o.removeEventListener)
  6629.  6629   o.removeEventListener(n, f, false);
  6630.  6630   else
  6631.  6631   o['on' + n] = null;
  6632.  6632   } catch (ex) {
  6633.  6633   // Might fail with permission denined on IE so we just ignore that
  6634.  6634   }
  6635.  6635   }
  6636.  6636   },
  6637.  6637  
  6638.  6638   _pageInit : function(win) {
  6639.  6639   var t = this;
  6640.  6640  
  6641.  6641   // Keep it from running more than once
  6642.  6642   if (t.domLoaded)
  6643.  6643   return;
  6644.  6644  
  6645.  6645   t.domLoaded = true;
  6646.  6646  
  6647.  6647   each(t.inits, function(c) {
  6648.  6648   c();
  6649.  6649   });
  6650.  6650  
  6651.  6651   t.inits = [];
  6652.  6652   },
  6653.  6653  
  6654.  6654   _wait : function(win) {
  6655.  6655   var t = this, doc = win.document;
  6656.  6656  
  6657.  6657   // No need since the document is already loaded
  6658.  6658   if (win.tinyMCE_GZ && tinyMCE_GZ.loaded) {
  6659.  6659   t.domLoaded = 1;
  6660.  6660   return;
  6661.  6661   }
  6662.  6662  
  6663.  6663   // Use IE method
  6664.  6664   if (doc.attachEvent) {
  6665.  6665   doc.attachEvent("onreadystatechange", function() {
  6666.  6666   if (doc.readyState === "complete") {
  6667.  6667   doc.detachEvent("onreadystatechange", arguments.callee);
  6668.  6668   t._pageInit(win);
  6669.  6669   }
  6670.  6670   });
  6671.  6671  
  6672.  6672   if (doc.documentElement.doScroll && win == win.top) {
  6673.  6673   (function() {
  6674.  6674   if (t.domLoaded)
  6675.  6675   return;
  6676.  6676  
  6677.  6677   try {
  6678.  6678   // If IE is used, use the trick by Diego Perini
  6679.  6679   // http://javascript.nwbox.com/IEContentLoaded/
  6680.  6680   doc.documentElement.doScroll("left");
  6681.  6681   } catch (ex) {
  6682.  6682   setTimeout(arguments.callee, 0);
  6683.  6683   return;
  6684.  6684   }
  6685.  6685  
  6686.  6686   t._pageInit(win);
  6687.  6687   })();
  6688.  6688   }
  6689.  6689   } else if (doc.addEventListener) {
  6690.  6690   t._add(win, 'DOMContentLoaded', function() {
  6691.  6691   t._pageInit(win);
  6692.  6692   });
  6693.  6693   }
  6694.  6694  
  6695.  6695   t._add(win, 'load', function() {
  6696.  6696   t._pageInit(win);
  6697.  6697   });
  6698.  6698   },
  6699.  6699  
  6700.  6700   _stoppers : {
  6701.  6701   preventDefault : function() {
  6702.  6702   this.returnValue = false;
  6703.  6703   },
  6704.  6704  
  6705.  6705   stopPropagation : function() {
  6706.  6706   this.cancelBubble = true;
  6707.  6707   }
  6708.  6708   }
  6709.  6709   });
  6710.  6710  
  6711.  6711   Event = tinymce.dom.Event = new tinymce.dom.EventUtils();
  6712.  6712  
  6713.  6713   // Dispatch DOM content loaded event for IE and Safari
  6714.  6714   Event._wait(window);
  6715.  6715  
  6716.  6716   tinymce.addUnload(function() {
  6717.  6717   Event.destroy();
  6718.  6718   });
  6719.  6719  })(tinymce);
  6720.  6720  
  6721.  6721  (function(tinymce) {
  6722.  6722   tinymce.dom.Element = function(id, settings) {
  6723.  6723   var t = this, dom, el;
  6724.  6724  
  6725.  6725   t.settings = settings = settings || {};
  6726.  6726   t.id = id;
  6727.  6727   t.dom = dom = settings.dom || tinymce.DOM;
  6728.  6728  
  6729.  6729   // Only IE leaks DOM references, this is a lot faster
  6730.  6730   if (!tinymce.isIE)
  6731.  6731   el = dom.get(t.id);
  6732.  6732  
  6733.  6733   tinymce.each(
  6734.  6734   ('getPos,getRect,getParent,add,setStyle,getStyle,setStyles,' +
  6735.  6735   'setAttrib,setAttribs,getAttrib,addClass,removeClass,' +
  6736.  6736   'hasClass,getOuterHTML,setOuterHTML,remove,show,hide,' +
  6737.  6737   'isHidden,setHTML,get').split(/,/)
  6738.  6738   , function(k) {
  6739.  6739   t[k] = function() {
  6740.  6740   var a = [id], i;
  6741.  6741  
  6742.  6742   for (i = 0; i < arguments.length; i++)
  6743.  6743   a.push(arguments[i]);
  6744.  6744  
  6745.  6745   a = dom[k].apply(dom, a);
  6746.  6746   t.update(k);
  6747.  6747  
  6748.  6748   return a;
  6749.  6749   };
  6750.  6750   });
  6751.  6751  
  6752.  6752   tinymce.extend(t, {
  6753.  6753   on : function(n, f, s) {
  6754.  6754   return tinymce.dom.Event.add(t.id, n, f, s);
  6755.  6755   },
  6756.  6756  
  6757.  6757   getXY : function() {
  6758.  6758   return {
  6759.  6759   x : parseInt(t.getStyle('left')),
  6760.  6760   y : parseInt(t.getStyle('top'))
  6761.  6761   };
  6762.  6762   },
  6763.  6763  
  6764.  6764   getSize : function() {
  6765.  6765   var n = dom.get(t.id);
  6766.  6766  
  6767.  6767   return {
  6768.  6768   w : parseInt(t.getStyle('width') || n.clientWidth),
  6769.  6769   h : parseInt(t.getStyle('height') || n.clientHeight)
  6770.  6770   };
  6771.  6771   },
  6772.  6772  
  6773.  6773   moveTo : function(x, y) {
  6774.  6774   t.setStyles({left : x, top : y});
  6775.  6775   },
  6776.  6776  
  6777.  6777   moveBy : function(x, y) {
  6778.  6778   var p = t.getXY();
  6779.  6779  
  6780.  6780   t.moveTo(p.x + x, p.y + y);
  6781.  6781   },
  6782.  6782  
  6783.  6783   resizeTo : function(w, h) {
  6784.  6784   t.setStyles({width : w, height : h});
  6785.  6785   },
  6786.  6786  
  6787.  6787   resizeBy : function(w, h) {
  6788.  6788   var s = t.getSize();
  6789.  6789  
  6790.  6790   t.resizeTo(s.w + w, s.h + h);
  6791.  6791   },
  6792.  6792  
  6793.  6793   update : function(k) {
  6794.  6794   var b;
  6795.  6795  
  6796.  6796   if (tinymce.isIE6 && settings.blocker) {
  6797.  6797   k = k || '';
  6798.  6798  
  6799.  6799   // Ignore getters
  6800.  6800   if (k.indexOf('get') === 0 || k.indexOf('has') === 0 || k.indexOf('is') === 0)
  6801.  6801   return;
  6802.  6802  
  6803.  6803   // Remove blocker on remove
  6804.  6804   if (k == 'remove') {
  6805.  6805   dom.remove(t.blocker);
  6806.  6806   return;
  6807.  6807   }
  6808.  6808  
  6809.  6809   if (!t.blocker) {
  6810.  6810   t.blocker = dom.uniqueId();
  6811.  6811   b = dom.add(settings.container || dom.getRoot(), 'iframe', {id : t.blocker, style : 'position:absolute;', frameBorder : 0, src : 'javascript:""'});
  6812.  6812   dom.setStyle(b, 'opacity', 0);
  6813.  6813   } else
  6814.  6814   b = dom.get(t.blocker);
  6815.  6815  
  6816.  6816   dom.setStyles(b, {
  6817.  6817   left : t.getStyle('left', 1),
  6818.  6818   top : t.getStyle('top', 1),
  6819.  6819   width : t.getStyle('width', 1),
  6820.  6820   height : t.getStyle('height', 1),
  6821.  6821   display : t.getStyle('display', 1),
  6822.  6822   zIndex : parseInt(t.getStyle('zIndex', 1) || 0) - 1
  6823.  6823   });
  6824.  6824   }
  6825.  6825   }
  6826.  6826   });
  6827.  6827   };
  6828.  6828  })(tinymce);
  6829.  6829  
  6830.  6830  (function(tinymce) {
  6831.  6831   function trimNl(s) {
  6832.  6832   return s.replace(/[\n\r]+/g, '');
  6833.  6833   };
  6834.  6834  
  6835.  6835   // Shorten names
  6836.  6836   var is = tinymce.is, isIE = tinymce.isIE, each = tinymce.each;
  6837.  6837  
  6838.  6838   tinymce.create('tinymce.dom.Selection', {
  6839.  6839   Selection : function(dom, win, serializer) {
  6840.  6840   var t = this;
  6841.  6841  
  6842.  6842   t.dom = dom;
  6843.  6843   t.win = win;
  6844.  6844   t.serializer = serializer;
  6845.  6845  
  6846.  6846   // Add events
  6847.  6847   each([
  6848.  6848   'onBeforeSetContent',
  6849.  6849  
  6850.  6850   'onBeforeGetContent',
  6851.  6851  
  6852.  6852   'onSetContent',
  6853.  6853  
  6854.  6854   'onGetContent'
  6855.  6855   ], function(e) {
  6856.  6856   t[e] = new tinymce.util.Dispatcher(t);
  6857.  6857   });
  6858.  6858  
  6859.  6859   // No W3C Range support
  6860.  6860   if (!t.win.getSelection)
  6861.  6861   t.tridentSel = new tinymce.dom.TridentSelection(t);
  6862.  6862  
  6863.  6863   if (tinymce.isIE && dom.boxModel)
  6864.  6864   this._fixIESelection();
  6865.  6865  
  6866.  6866   // Prevent leaks
  6867.  6867   tinymce.addUnload(t.destroy, t);
  6868.  6868   },
  6869.  6869  
  6870.  6870   getContent : function(s) {
  6871.  6871   var t = this, r = t.getRng(), e = t.dom.create("body"), se = t.getSel(), wb, wa, n;
  6872.  6872  
  6873.  6873   s = s || {};
  6874.  6874   wb = wa = '';
  6875.  6875   s.get = true;
  6876.  6876   s.format = s.format || 'html';
  6877.  6877   t.onBeforeGetContent.dispatch(t, s);
  6878.  6878  
  6879.  6879   if (s.format == 'text')
  6880.  6880   return t.isCollapsed() ? '' : (r.text || (se.toString ? se.toString() : ''));
  6881.  6881  
  6882.  6882   if (r.cloneContents) {
  6883.  6883   n = r.cloneContents();
  6884.  6884  
  6885.  6885   if (n)
  6886.  6886   e.appendChild(n);
  6887.  6887   } else if (is(r.item) || is(r.htmlText))
  6888.  6888   e.innerHTML = r.item ? r.item(0).outerHTML : r.htmlText;
  6889.  6889   else
  6890.  6890   e.innerHTML = r.toString();
  6891.  6891  
  6892.  6892   // Keep whitespace before and after
  6893.  6893   if (/^\s/.test(e.innerHTML))
  6894.  6894   wb = ' ';
  6895.  6895  
  6896.  6896   if (/\s+$/.test(e.innerHTML))
  6897.  6897   wa = ' ';
  6898.  6898  
  6899.  6899   s.getInner = true;
  6900.  6900  
  6901.  6901   s.content = t.isCollapsed() ? '' : wb + t.serializer.serialize(e, s) + wa;
  6902.  6902   t.onGetContent.dispatch(t, s);
  6903.  6903  
  6904.  6904   return s.content;
  6905.  6905   },
  6906.  6906  
  6907.  6907   setContent : function(content, args) {
  6908.  6908   var self = this, rng = self.getRng(), caretNode, doc = self.win.document, frag, temp;
  6909.  6909  
  6910.  6910   args = args || {format : 'html'};
  6911.  6911   args.set = true;
  6912.  6912   content = args.content = content;
  6913.  6913  
  6914.  6914   // Dispatch before set content event
  6915.  6915   if (!args.no_events)
  6916.  6916   self.onBeforeSetContent.dispatch(self, args);
  6917.  6917  
  6918.  6918   content = args.content;
  6919.  6919  
  6920.  6920   if (rng.insertNode) {
  6921.  6921   // Make caret marker since insertNode places the caret in the beginning of text after insert
  6922.  6922   content += '<span id="__caret">_</span>';
  6923.  6923  
  6924.  6924   // Delete and insert new node
  6925.  6925   if (rng.startContainer == doc && rng.endContainer == doc) {
  6926.  6926   // WebKit will fail if the body is empty since the range is then invalid and it can't insert contents
  6927.  6927   doc.body.innerHTML = content;
  6928.  6928   } else {
  6929.  6929   rng.deleteContents();
  6930.  6930  
  6931.  6931   if (doc.body.childNodes.length == 0) {
  6932.  6932   doc.body.innerHTML = content;
  6933.  6933   } else {
  6934.  6934   // createContextualFragment doesn't exists in IE 9 DOMRanges
  6935.  6935   if (rng.createContextualFragment) {
  6936.  6936   rng.insertNode(rng.createContextualFragment(content));
  6937.  6937   } else {
  6938.  6938   // Fake createContextualFragment call in IE 9
  6939.  6939   frag = doc.createDocumentFragment();
  6940.  6940   temp = doc.createElement('div');
  6941.  6941  
  6942.  6942   frag.appendChild(temp);
  6943.  6943   temp.outerHTML = content;
  6944.  6944  
  6945.  6945   rng.insertNode(frag);
  6946.  6946   }
  6947.  6947   }
  6948.  6948   }
  6949.  6949  
  6950.  6950   // Move to caret marker
  6951.  6951   caretNode = self.dom.get('__caret');
  6952.  6952  
  6953.  6953   // Make sure we wrap it compleatly, Opera fails with a simple select call
  6954.  6954   rng = doc.createRange();
  6955.  6955   rng.setStartBefore(caretNode);
  6956.  6956   rng.setEndBefore(caretNode);
  6957.  6957   self.setRng(rng);
  6958.  6958  
  6959.  6959   // Remove the caret position
  6960.  6960   self.dom.remove('__caret');
  6961.  6961   self.setRng(rng);
  6962.  6962   } else {
  6963.  6963   if (rng.item) {
  6964.  6964   // Delete content and get caret text selection
  6965.  6965   doc.execCommand('Delete', false, null);
  6966.  6966   rng = self.getRng();
  6967.  6967   }
  6968.  6968  
  6969.  6969   rng.pasteHTML(content);
  6970.  6970   }
  6971.  6971  
  6972.  6972   // Dispatch set content event
  6973.  6973   if (!args.no_events)
  6974.  6974   self.onSetContent.dispatch(self, args);
  6975.  6975   },
  6976.  6976  
  6977.  6977   getStart : function() {
  6978.  6978   var rng = this.getRng(), startElement, parentElement, checkRng, node;
  6979.  6979  
  6980.  6980   if (rng.duplicate || rng.item) {
  6981.  6981   // Control selection, return first item
  6982.  6982   if (rng.item)
  6983.  6983   return rng.item(0);
  6984.  6984  
  6985.  6985   // Get start element
  6986.  6986   checkRng = rng.duplicate();
  6987.  6987   checkRng.collapse(1);
  6988.  6988   startElement = checkRng.parentElement();
  6989.  6989  
  6990.  6990   // Check if range parent is inside the start element, then return the inner parent element
  6991.  6991   // This will fix issues when a single element is selected, IE would otherwise return the wrong start element
  6992.  6992   parentElement = node = rng.parentElement();
  6993.  6993   while (node = node.parentNode) {
  6994.  6994   if (node == startElement) {
  6995.  6995   startElement = parentElement;
  6996.  6996   break;
  6997.  6997   }
  6998.  6998   }
  6999.  6999  
  7000.  7000   return startElement;
  7001.  7001   } else {
  7002.  7002   startElement = rng.startContainer;
  7003.  7003  
  7004.  7004   if (startElement.nodeType == 1 && startElement.hasChildNodes())
  7005.  7005   startElement = startElement.childNodes[Math.min(startElement.childNodes.length - 1, rng.startOffset)];
  7006.  7006  
  7007.  7007   if (startElement && startElement.nodeType == 3)
  7008.  7008   return startElement.parentNode;
  7009.  7009  
  7010.  7010   return startElement;
  7011.  7011   }
  7012.  7012   },
  7013.  7013  
  7014.  7014   getEnd : function() {
  7015.  7015   var t = this, r = t.getRng(), e, eo;
  7016.  7016  
  7017.  7017   if (r.duplicate || r.item) {
  7018.  7018   if (r.item)
  7019.  7019   return r.item(0);
  7020.  7020  
  7021.  7021   r = r.duplicate();
  7022.  7022   r.collapse(0);
  7023.  7023   e = r.parentElement();
  7024.  7024  
  7025.  7025   if (e && e.nodeName == 'BODY')
  7026.  7026   return e.lastChild || e;
  7027.  7027  
  7028.  7028   return e;
  7029.  7029   } else {
  7030.  7030   e = r.endContainer;
  7031.  7031   eo = r.endOffset;
  7032.  7032  
  7033.  7033   if (e.nodeType == 1 && e.hasChildNodes())
  7034.  7034   e = e.childNodes[eo > 0 ? eo - 1 : eo];
  7035.  7035  
  7036.  7036   if (e && e.nodeType == 3)
  7037.  7037   return e.parentNode;
  7038.  7038  
  7039.  7039   return e;
  7040.  7040   }
  7041.  7041   },
  7042.  7042  
  7043.  7043   getBookmark : function(type, normalized) {
  7044.  7044   var t = this, dom = t.dom, rng, rng2, id, collapsed, name, element, index, chr = '\uFEFF', styles;
  7045.  7045  
  7046.  7046   function findIndex(name, element) {
  7047.  7047   var index = 0;
  7048.  7048  
  7049.  7049   each(dom.select(name), function(node, i) {
  7050.  7050   if (node == element)
  7051.  7051   index = i;
  7052.  7052   });
  7053.  7053  
  7054.  7054   return index;
  7055.  7055   };
  7056.  7056  
  7057.  7057   if (type == 2) {
  7058.  7058   function getLocation() {
  7059.  7059   var rng = t.getRng(true), root = dom.getRoot(), bookmark = {};
  7060.  7060  
  7061.  7061   function getPoint(rng, start) {
  7062.  7062   var container = rng[start ? 'startContainer' : 'endContainer'],
  7063.  7063   offset = rng[start ? 'startOffset' : 'endOffset'], point = [], node, childNodes, after = 0;
  7064.  7064  
  7065.  7065   if (container.nodeType == 3) {
  7066.  7066   if (normalized) {
  7067.  7067   for (node = container.previousSibling; node && node.nodeType == 3; node = node.previousSibling)
  7068.  7068   offset += node.nodeValue.length;
  7069.  7069   }
  7070.  7070  
  7071.  7071   point.push(offset);
  7072.  7072   } else {
  7073.  7073   childNodes = container.childNodes;
  7074.  7074  
  7075.  7075   if (offset >= childNodes.length && childNodes.length) {
  7076.  7076   after = 1;
  7077.  7077   offset = Math.max(0, childNodes.length - 1);
  7078.  7078   }
  7079.  7079  
  7080.  7080   point.push(t.dom.nodeIndex(childNodes[offset], normalized) + after);
  7081.  7081   }
  7082.  7082  
  7083.  7083   for (; container && container != root; container = container.parentNode)
  7084.  7084   point.push(t.dom.nodeIndex(container, normalized));
  7085.  7085  
  7086.  7086   return point;
  7087.  7087   };
  7088.  7088  
  7089.  7089   bookmark.start = getPoint(rng, true);
  7090.  7090  
  7091.  7091   if (!t.isCollapsed())
  7092.  7092   bookmark.end = getPoint(rng);
  7093.  7093  
  7094.  7094   return bookmark;
  7095.  7095   };
  7096.  7096  
  7097.  7097   return getLocation();
  7098.  7098   }
  7099.  7099  
  7100.  7100   // Handle simple range
  7101.  7101   if (type)
  7102.  7102   return {rng : t.getRng()};
  7103.  7103  
  7104.  7104   rng = t.getRng();
  7105.  7105   id = dom.uniqueId();
  7106.  7106   collapsed = tinyMCE.activeEditor.selection.isCollapsed();
  7107.  7107   styles = 'overflow:hidden;line-height:0px';
  7108.  7108  
  7109.  7109   // Explorer method
  7110.  7110   if (rng.duplicate || rng.item) {
  7111.  7111   // Text selection
  7112.  7112   if (!rng.item) {
  7113.  7113   rng2 = rng.duplicate();
  7114.  7114  
  7115.  7115   try {
  7116.  7116   // Insert start marker
  7117.  7117   rng.collapse();
  7118.  7118   rng.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_start" style="' + styles + '">' + chr + '</span>');
  7119.  7119  
  7120.  7120   // Insert end marker
  7121.  7121   if (!collapsed) {
  7122.  7122   rng2.collapse(false);
  7123.  7123  
  7124.  7124   // Detect the empty space after block elements in IE and move the end back one character <p></p>] becomes <p>]</p>
  7125.  7125   rng.moveToElementText(rng2.parentElement());
  7126.  7126   if (rng.compareEndPoints('StartToEnd', rng2) == 0)
  7127.  7127   rng2.move('character', -1);
  7128.  7128  
  7129.  7129   rng2.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_end" style="' + styles + '">' + chr + '</span>');
  7130.  7130   }
  7131.  7131   } catch (ex) {
  7132.  7132   // IE might throw unspecified error so lets ignore it
  7133.  7133   return null;
  7134.  7134   }
  7135.  7135   } else {
  7136.  7136   // Control selection
  7137.  7137   element = rng.item(0);
  7138.  7138   name = element.nodeName;
  7139.  7139  
  7140.  7140   return {name : name, index : findIndex(name, element)};
  7141.  7141   }
  7142.  7142   } else {
  7143.  7143   element = t.getNode();
  7144.  7144   name = element.nodeName;
  7145.  7145   if (name == 'IMG')
  7146.  7146   return {name : name, index : findIndex(name, element)};
  7147.  7147  
  7148.  7148   // W3C method
  7149.  7149   rng2 = rng.cloneRange();
  7150.  7150  
  7151.  7151   // Insert end marker
  7152.  7152   if (!collapsed) {
  7153.  7153   rng2.collapse(false);
  7154.  7154   rng2.insertNode(dom.create('span', {'data-mce-type' : "bookmark", id : id + '_end', style : styles}, chr));
  7155.  7155   }
  7156.  7156  
  7157.  7157   rng.collapse(true);
  7158.  7158   rng.insertNode(dom.create('span', {'data-mce-type' : "bookmark", id : id + '_start', style : styles}, chr));
  7159.  7159   }
  7160.  7160  
  7161.  7161   t.moveToBookmark({id : id, keep : 1});
  7162.  7162  
  7163.  7163   return {id : id};
  7164.  7164   },
  7165.  7165  
  7166.  7166   moveToBookmark : function(bookmark) {
  7167.  7167   var t = this, dom = t.dom, marker1, marker2, rng, root, startContainer, endContainer, startOffset, endOffset;
  7168.  7168  
  7169.  7169   // Clear selection cache
  7170.  7170   if (t.tridentSel)
  7171.  7171   t.tridentSel.destroy();
  7172.  7172  
  7173.  7173   if (bookmark) {
  7174.  7174   if (bookmark.start) {
  7175.  7175   rng = dom.createRng();
  7176.  7176   root = dom.getRoot();
  7177.  7177  
  7178.  7178   function setEndPoint(start) {
  7179.  7179   var point = bookmark[start ? 'start' : 'end'], i, node, offset, children;
  7180.  7180  
  7181.  7181   if (point) {
  7182.  7182   offset = point[0];
  7183.  7183  
  7184.  7184   // Find container node
  7185.  7185   for (node = root, i = point.length - 1; i >= 1; i--) {
  7186.  7186   children = node.childNodes;
  7187.  7187  
  7188.  7188   if (point[i] > children.length - 1)
  7189.  7189   return;
  7190.  7190  
  7191.  7191   node = children[point[i]];
  7192.  7192   }
  7193.  7193  
  7194.  7194   // Move text offset to best suitable location
  7195.  7195   if (node.nodeType === 3)
  7196.  7196   offset = Math.min(point[0], node.nodeValue.length);
  7197.  7197  
  7198.  7198   // Move element offset to best suitable location
  7199.  7199   if (node.nodeType === 1)
  7200.  7200   offset = Math.min(point[0], node.childNodes.length);
  7201.  7201  
  7202.  7202   // Set offset within container node
  7203.  7203   if (start)
  7204.  7204   rng.setStart(node, offset);
  7205.  7205   else
  7206.  7206   rng.setEnd(node, offset);
  7207.  7207   }
  7208.  7208  
  7209.  7209   return true;
  7210.  7210   };
  7211.  7211  
  7212.  7212   if (setEndPoint(true) && setEndPoint()) {
  7213.  7213   t.setRng(rng);
  7214.  7214   }
  7215.  7215   } else if (bookmark.id) {
  7216.  7216   function restoreEndPoint(suffix) {
  7217.  7217   var marker = dom.get(bookmark.id + '_' + suffix), node, idx, next, prev, keep = bookmark.keep;
  7218.  7218  
  7219.  7219   if (marker) {
  7220.  7220   node = marker.parentNode;
  7221.  7221  
  7222.  7222   if (suffix == 'start') {
  7223.  7223   if (!keep) {
  7224.  7224   idx = dom.nodeIndex(marker);
  7225.  7225   } else {
  7226.  7226   node = marker.firstChild;
  7227.  7227   idx = 1;
  7228.  7228   }
  7229.  7229  
  7230.  7230   startContainer = endContainer = node;
  7231.  7231   startOffset = endOffset = idx;
  7232.  7232   } else {
  7233.  7233   if (!keep) {
  7234.  7234   idx = dom.nodeIndex(marker);
  7235.  7235   } else {
  7236.  7236   node = marker.firstChild;
  7237.  7237   idx = 1;
  7238.  7238   }
  7239.  7239  
  7240.  7240   endContainer = node;
  7241.  7241   endOffset = idx;
  7242.  7242   }
  7243.  7243  
  7244.  7244   if (!keep) {
  7245.  7245   prev = marker.previousSibling;
  7246.  7246   next = marker.nextSibling;
  7247.  7247  
  7248.  7248   // Remove all marker text nodes
  7249.  7249   each(tinymce.grep(marker.childNodes), function(node) {
  7250.  7250   if (node.nodeType == 3)
  7251.  7251   node.nodeValue = node.nodeValue.replace(/\uFEFF/g, '');
  7252.  7252   });
  7253.  7253  
  7254.  7254   // Remove marker but keep children if for example contents where inserted into the marker
  7255.  7255   // Also remove duplicated instances of the marker for example by a split operation or by WebKit auto split on paste feature
  7256.  7256   while (marker = dom.get(bookmark.id + '_' + suffix))
  7257.  7257   dom.remove(marker, 1);
  7258.  7258  
  7259.  7259   // If siblings are text nodes then merge them unless it's Opera since it some how removes the node
  7260.  7260   // and we are sniffing since adding a lot of detection code for a browser with 3% of the market isn't worth the effort. Sorry, Opera but it's just a fact
  7261.  7261   if (prev && next && prev.nodeType == next.nodeType && prev.nodeType == 3 && !tinymce.isOpera) {
  7262.  7262   idx = prev.nodeValue.length;
  7263.  7263   prev.appendData(next.nodeValue);
  7264.  7264   dom.remove(next);
  7265.  7265  
  7266.  7266   if (suffix == 'start') {
  7267.  7267   startContainer = endContainer = prev;
  7268.  7268   startOffset = endOffset = idx;
  7269.  7269   } else {
  7270.  7270   endContainer = prev;
  7271.  7271   endOffset = idx;
  7272.  7272   }
  7273.  7273   }
  7274.  7274   }
  7275.  7275   }
  7276.  7276   };
  7277.  7277  
  7278.  7278   function addBogus(node) {
  7279.  7279   // Adds a bogus BR element for empty block elements or just a space on IE since it renders BR elements incorrectly
  7280.  7280   if (dom.isBlock(node) && !node.innerHTML)
  7281.  7281   node.innerHTML = !isIE ? '<br data-mce-bogus="1" />' : ' ';
  7282.  7282  
  7283.  7283   return node;
  7284.  7284   };
  7285.  7285  
  7286.  7286   // Restore start/end points
  7287.  7287   restoreEndPoint('start');
  7288.  7288   restoreEndPoint('end');
  7289.  7289  
  7290.  7290   if (startContainer) {
  7291.  7291   rng = dom.createRng();
  7292.  7292   rng.setStart(addBogus(startContainer), startOffset);
  7293.  7293   rng.setEnd(addBogus(endContainer), endOffset);
  7294.  7294   t.setRng(rng);
  7295.  7295   }
  7296.  7296   } else if (bookmark.name) {
  7297.  7297   t.select(dom.select(bookmark.name)[bookmark.index]);
  7298.  7298   } else if (bookmark.rng)
  7299.  7299   t.setRng(bookmark.rng);
  7300.  7300   }
  7301.  7301   },
  7302.  7302  
  7303.  7303   select : function(node, content) {
  7304.  7304   var t = this, dom = t.dom, rng = dom.createRng(), idx;
  7305.  7305  
  7306.  7306   if (node) {
  7307.  7307   idx = dom.nodeIndex(node);
  7308.  7308   rng.setStart(node.parentNode, idx);
  7309.  7309   rng.setEnd(node.parentNode, idx + 1);
  7310.  7310  
  7311.  7311   // Find first/last text node or BR element
  7312.  7312   if (content) {
  7313.  7313   function setPoint(node, start) {
  7314.  7314   var walker = new tinymce.dom.TreeWalker(node, node);
  7315.  7315  
  7316.  7316   do {
  7317.  7317   // Text node
  7318.  7318   if (node.nodeType == 3 && tinymce.trim(node.nodeValue).length != 0) {
  7319.  7319   if (start)
  7320.  7320   rng.setStart(node, 0);
  7321.  7321   else
  7322.  7322   rng.setEnd(node, node.nodeValue.length);
  7323.  7323  
  7324.  7324   return;
  7325.  7325   }
  7326.  7326  
  7327.  7327   // BR element
  7328.  7328   if (node.nodeName == 'BR') {
  7329.  7329   if (start)
  7330.  7330   rng.setStartBefore(node);
  7331.  7331   else
  7332.  7332   rng.setEndBefore(node);
  7333.  7333  
  7334.  7334   return;
  7335.  7335   }
  7336.  7336   } while (node = (start ? walker.next() : walker.prev()));
  7337.  7337   };
  7338.  7338  
  7339.  7339   setPoint(node, 1);
  7340.  7340   setPoint(node);
  7341.  7341   }
  7342.  7342  
  7343.  7343   t.setRng(rng);
  7344.  7344   }
  7345.  7345  
  7346.  7346   return node;
  7347.  7347   },
  7348.  7348  
  7349.  7349   isCollapsed : function() {
  7350.  7350   var t = this, r = t.getRng(), s = t.getSel();
  7351.  7351  
  7352.  7352   if (!r || r.item)
  7353.  7353   return false;
  7354.  7354  
  7355.  7355   if (r.compareEndPoints)
  7356.  7356   return r.compareEndPoints('StartToEnd', r) === 0;
  7357.  7357  
  7358.  7358   return !s || r.collapsed;
  7359.  7359   },
  7360.  7360  
  7361.  7361   collapse : function(to_start) {
  7362.  7362   var self = this, rng = self.getRng(), node;
  7363.  7363  
  7364.  7364   // Control range on IE
  7365.  7365   if (rng.item) {
  7366.  7366   node = rng.item(0);
  7367.  7367   rng = self.win.document.body.createTextRange();
  7368.  7368   rng.moveToElementText(node);
  7369.  7369   }
  7370.  7370  
  7371.  7371   rng.collapse(!!to_start);
  7372.  7372   self.setRng(rng);
  7373.  7373   },
  7374.  7374  
  7375.  7375   getSel : function() {
  7376.  7376   var t = this, w = this.win;
  7377.  7377  
  7378.  7378   return w.getSelection ? w.getSelection() : w.document.selection;
  7379.  7379   },
  7380.  7380  
  7381.  7381   getRng : function(w3c) {
  7382.  7382   var t = this, s, r, elm, doc = t.win.document;
  7383.  7383  
  7384.  7384   // Found tridentSel object then we need to use that one
  7385.  7385   if (w3c && t.tridentSel)
  7386.  7386   return t.tridentSel.getRangeAt(0);
  7387.  7387  
  7388.  7388   try {
  7389.  7389   if (s = t.getSel())
  7390.  7390   r = s.rangeCount > 0 ? s.getRangeAt(0) : (s.createRange ? s.createRange() : doc.createRange());
  7391.  7391   } catch (ex) {
  7392.  7392   // IE throws unspecified error here if TinyMCE is placed in a frame/iframe
  7393.  7393   }
  7394.  7394  
  7395.  7395   // We have W3C ranges and it's IE then fake control selection since IE9 doesn't handle that correctly yet
  7396.  7396   if (tinymce.isIE && r && r.setStart && doc.selection.createRange().item) {
  7397.  7397   elm = doc.selection.createRange().item(0);
  7398.  7398   r = doc.createRange();
  7399.  7399   r.setStartBefore(elm);
  7400.  7400   r.setEndAfter(elm);
  7401.  7401   }
  7402.  7402  
  7403.  7403   // No range found then create an empty one
  7404.  7404   // This can occur when the editor is placed in a hidden container element on Gecko
  7405.  7405   // Or on IE when there was an exception
  7406.  7406   if (!r)
  7407.  7407   r = doc.createRange ? doc.createRange() : doc.body.createTextRange();
  7408.  7408  
  7409.  7409   if (t.selectedRange && t.explicitRange) {
  7410.  7410   if (r.compareBoundaryPoints(r.START_TO_START, t.selectedRange) === 0 && r.compareBoundaryPoints(r.END_TO_END, t.selectedRange) === 0) {
  7411.  7411   // Safari, Opera and Chrome only ever select text which causes the range to change.
  7412.  7412   // This lets us use the originally set range if the selection hasn't been changed by the user.
  7413.  7413   r = t.explicitRange;
  7414.  7414   } else {
  7415.  7415   t.selectedRange = null;
  7416.  7416   t.explicitRange = null;
  7417.  7417   }
  7418.  7418   }
  7419.  7419  
  7420.  7420   return r;
  7421.  7421   },
  7422.  7422  
  7423.  7423   setRng : function(r) {
  7424.  7424   var s, t = this;
  7425.  7425  
  7426.  7426   if (!t.tridentSel) {
  7427.  7427   s = t.getSel();
  7428.  7428  
  7429.  7429   if (s) {
  7430.  7430   t.explicitRange = r;
  7431.  7431   s.removeAllRanges();
  7432.  7432   s.addRange(r);
  7433.  7433   t.selectedRange = s.getRangeAt(0);
  7434.  7434   }
  7435.  7435   } else {
  7436.  7436   // Is W3C Range
  7437.  7437   if (r.cloneRange) {
  7438.  7438   t.tridentSel.addRange(r);
  7439.  7439   return;
  7440.  7440   }
  7441.  7441  
  7442.  7442   // Is IE specific range
  7443.  7443   try {
  7444.  7444   r.select();
  7445.  7445   } catch (ex) {
  7446.  7446   // Needed for some odd IE bug #1843306
  7447.  7447   }
  7448.  7448   }
  7449.  7449   },
  7450.  7450  
  7451.  7451   setNode : function(n) {
  7452.  7452   var t = this;
  7453.  7453  
  7454.  7454   t.setContent(t.dom.getOuterHTML(n));
  7455.  7455  
  7456.  7456   return n;
  7457.  7457   },
  7458.  7458  
  7459.  7459   getNode : function() {
  7460.  7460   var t = this, rng = t.getRng(), sel = t.getSel(), elm, start = rng.startContainer, end = rng.endContainer;
  7461.  7461  
  7462.  7462   // Range maybe lost after the editor is made visible again
  7463.  7463   if (!rng)
  7464.  7464   return t.dom.getRoot();
  7465.  7465  
  7466.  7466   if (rng.setStart) {
  7467.  7467   elm = rng.commonAncestorContainer;
  7468.  7468  
  7469.  7469   // Handle selection a image or other control like element such as anchors
  7470.  7470   if (!rng.collapsed) {
  7471.  7471   if (rng.startContainer == rng.endContainer) {
  7472.  7472   if (rng.endOffset - rng.startOffset < 2) {
  7473.  7473   if (rng.startContainer.hasChildNodes())
  7474.  7474   elm = rng.startContainer.childNodes[rng.startOffset];
  7475.  7475   }
  7476.  7476   }
  7477.  7477  
  7478.  7478   // If the anchor node is a element instead of a text node then return this element
  7479.  7479   //if (tinymce.isWebKit && sel.anchorNode && sel.anchorNode.nodeType == 1)
  7480.  7480   // return sel.anchorNode.childNodes[sel.anchorOffset];
  7481.  7481  
  7482.  7482   // Handle cases where the selection is immediately wrapped around a node and return that node instead of it's parent.
  7483.  7483   // This happens when you double click an underlined word in FireFox.
  7484.  7484   if (start.nodeType === 3 && end.nodeType === 3) {
  7485.  7485   function skipEmptyTextNodes(n, forwards) {
  7486.  7486   var orig = n;
  7487.  7487   while (n && n.nodeType === 3 && n.length === 0) {
  7488.  7488   n = forwards ? n.nextSibling : n.previousSibling;
  7489.  7489   }
  7490.  7490   return n || orig;
  7491.  7491   }
  7492.  7492   if (start.length === rng.startOffset) {
  7493.  7493   start = skipEmptyTextNodes(start.nextSibling, true);
  7494.  7494   } else {
  7495.  7495   start = start.parentNode;
  7496.  7496   }
  7497.  7497   if (rng.endOffset === 0) {
  7498.  7498   end = skipEmptyTextNodes(end.previousSibling, false);
  7499.  7499   } else {
  7500.  7500   end = end.parentNode;
  7501.  7501   }
  7502.  7502  
  7503.  7503   if (start && start === end)
  7504.  7504   return start;
  7505.  7505   }
  7506.  7506   }
  7507.  7507  
  7508.  7508   if (elm && elm.nodeType == 3)
  7509.  7509   return elm.parentNode;
  7510.  7510  
  7511.  7511   return elm;
  7512.  7512   }
  7513.  7513  
  7514.  7514   return rng.item ? rng.item(0) : rng.parentElement();
  7515.  7515   },
  7516.  7516  
  7517.  7517   getSelectedBlocks : function(st, en) {
  7518.  7518   var t = this, dom = t.dom, sb, eb, n, bl = [];
  7519.  7519  
  7520.  7520   sb = dom.getParent(st || t.getStart(), dom.isBlock);
  7521.  7521   eb = dom.getParent(en || t.getEnd(), dom.isBlock);
  7522.  7522  
  7523.  7523   if (sb)
  7524.  7524   bl.push(sb);
  7525.  7525  
  7526.  7526   if (sb && eb && sb != eb) {
  7527.  7527   n = sb;
  7528.  7528  
  7529.  7529   while ((n = n.nextSibling) && n != eb) {
  7530.  7530   if (dom.isBlock(n))
  7531.  7531   bl.push(n);
  7532.  7532   }
  7533.  7533   }
  7534.  7534  
  7535.  7535   if (eb && sb != eb)
  7536.  7536   bl.push(eb);
  7537.  7537  
  7538.  7538   return bl;
  7539.  7539   },
  7540.  7540  
  7541.  7541   destroy : function(s) {
  7542.  7542   var t = this;
  7543.  7543  
  7544.  7544   t.win = null;
  7545.  7545  
  7546.  7546   if (t.tridentSel)
  7547.  7547   t.tridentSel.destroy();
  7548.  7548  
  7549.  7549   // Manual destroy then remove unload handler
  7550.  7550   if (!s)
  7551.  7551   tinymce.removeUnload(t.destroy);
  7552.  7552   },
  7553.  7553  
  7554.  7554   // IE has an issue where you can't select/move the caret by clicking outside the body if the document is in standards mode
  7555.  7555   _fixIESelection : function() {
  7556.  7556   var dom = this.dom, doc = dom.doc, body = doc.body, started, startRng, htmlElm;
  7557.  7557  
  7558.  7558   // Make HTML element unselectable since we are going to handle selection by hand
  7559.  7559   doc.documentElement.unselectable = true;
  7560.  7560  
  7561.  7561   // Return range from point or null if it failed
  7562.  7562   function rngFromPoint(x, y) {
  7563.  7563   var rng = body.createTextRange();
  7564.  7564  
  7565.  7565   try {
  7566.  7566   rng.moveToPoint(x, y);
  7567.  7567   } catch (ex) {
  7568.  7568   // IE sometimes throws and exception, so lets just ignore it
  7569.  7569   rng = null;
  7570.  7570   }
  7571.  7571  
  7572.  7572   return rng;
  7573.  7573   };
  7574.  7574  
  7575.  7575   // Fires while the selection is changing
  7576.  7576   function selectionChange(e) {
  7577.  7577   var pointRng;
  7578.  7578  
  7579.  7579   // Check if the button is down or not
  7580.  7580   if (e.button) {
  7581.  7581   // Create range from mouse position
  7582.  7582   pointRng = rngFromPoint(e.x, e.y);
  7583.  7583  
  7584.  7584   if (pointRng) {
  7585.  7585   // Check if pointRange is before/after selection then change the endPoint
  7586.  7586   if (pointRng.compareEndPoints('StartToStart', startRng) > 0)
  7587.  7587   pointRng.setEndPoint('StartToStart', startRng);
  7588.  7588   else
  7589.  7589   pointRng.setEndPoint('EndToEnd', startRng);
  7590.  7590  
  7591.  7591   pointRng.select();
  7592.  7592   }
  7593.  7593   } else
  7594.  7594   endSelection();
  7595.  7595   }
  7596.  7596  
  7597.  7597   // Removes listeners
  7598.  7598   function endSelection() {
  7599.  7599   var rng = doc.selection.createRange();
  7600.  7600  
  7601.  7601   // If the range is collapsed then use the last start range
  7602.  7602   if (startRng && !rng.item && rng.compareEndPoints('StartToEnd', rng) === 0)
  7603.  7603   startRng.select();
  7604.  7604  
  7605.  7605   dom.unbind(doc, 'mouseup', endSelection);
  7606.  7606   dom.unbind(doc, 'mousemove', selectionChange);
  7607.  7607   startRng = started = 0;
  7608.  7608   };
  7609.  7609  
  7610.  7610   // Detect when user selects outside BODY
  7611.  7611   dom.bind(doc, ['mousedown', 'contextmenu'], function(e) {
  7612.  7612   if (e.target.nodeName === 'HTML') {
  7613.  7613   if (started)
  7614.  7614   endSelection();
  7615.  7615  
  7616.  7616   // Detect vertical scrollbar, since IE will fire a mousedown on the scrollbar and have target set as HTML
  7617.  7617   htmlElm = doc.documentElement;
  7618.  7618   if (htmlElm.scrollHeight > htmlElm.clientHeight)
  7619.  7619   return;
  7620.  7620  
  7621.  7621   started = 1;
  7622.  7622   // Setup start position
  7623.  7623   startRng = rngFromPoint(e.x, e.y);
  7624.  7624   if (startRng) {
  7625.  7625   // Listen for selection change events
  7626.  7626   dom.bind(doc, 'mouseup', endSelection);
  7627.  7627   dom.bind(doc, 'mousemove', selectionChange);
  7628.  7628  
  7629.  7629   dom.win.focus();
  7630.  7630   startRng.select();
  7631.  7631   }
  7632.  7632   }
  7633.  7633   });
  7634.  7634   }
  7635.  7635   });
  7636.  7636  })(tinymce);
  7637.  7637  
  7638.  7638  (function(tinymce) {
  7639.  7639   tinymce.dom.Serializer = function(settings, dom, schema) {
  7640.  7640   var onPreProcess, onPostProcess, isIE = tinymce.isIE, each = tinymce.each, htmlParser;
  7641.  7641  
  7642.  7642   // Support the old apply_source_formatting option
  7643.  7643   if (!settings.apply_source_formatting)
  7644.  7644   settings.indent = false;
  7645.  7645  
  7646.  7646   settings.remove_trailing_brs = true;
  7647.  7647  
  7648.  7648   // Default DOM and Schema if they are undefined
  7649.  7649   dom = dom || tinymce.DOM;
  7650.  7650   schema = schema || new tinymce.html.Schema(settings);
  7651.  7651   settings.entity_encoding = settings.entity_encoding || 'named';
  7652.  7652  
  7653.  7653   onPreProcess = new tinymce.util.Dispatcher(self);
  7654.  7654  
  7655.  7655   onPostProcess = new tinymce.util.Dispatcher(self);
  7656.  7656  
  7657.  7657   htmlParser = new tinymce.html.DomParser(settings, schema);
  7658.  7658  
  7659.  7659   // Convert move data-mce-src, data-mce-href and data-mce-style into nodes or process them if needed
  7660.  7660   htmlParser.addAttributeFilter('src,href,style', function(nodes, name) {
  7661.  7661   var i = nodes.length, node, value, internalName = 'data-mce-' + name, urlConverter = settings.url_converter, urlConverterScope = settings.url_converter_scope, undef;
  7662.  7662  
  7663.  7663   while (i--) {
  7664.  7664   node = nodes[i];
  7665.  7665  
  7666.  7666   value = node.attributes.map[internalName];
  7667.  7667   if (value !== undef) {
  7668.  7668   // Set external name to internal value and remove internal
  7669.  7669   node.attr(name, value.length > 0 ? value : null);
  7670.  7670   node.attr(internalName, null);
  7671.  7671   } else {
  7672.  7672   // No internal attribute found then convert the value we have in the DOM
  7673.  7673   value = node.attributes.map[name];
  7674.  7674  
  7675.  7675   if (name === "style")
  7676.  7676   value = dom.serializeStyle(dom.parseStyle(value), node.name);
  7677.  7677   else if (urlConverter)
  7678.  7678   value = urlConverter.call(urlConverterScope, value, name, node.name);
  7679.  7679  
  7680.  7680   node.attr(name, value.length > 0 ? value : null);
  7681.  7681   }
  7682.  7682   }
  7683.  7683   });
  7684.  7684  
  7685.  7685   // Remove internal classes mceItem<..>
  7686.  7686   htmlParser.addAttributeFilter('class', function(nodes, name) {
  7687.  7687   var i = nodes.length, node, value;
  7688.  7688  
  7689.  7689   while (i--) {
  7690.  7690   node = nodes[i];
  7691.  7691   value = node.attr('class').replace(/\s*mce(Item\w+|Selected)\s*/g, '');
  7692.  7692   node.attr('class', value.length > 0 ? value : null);
  7693.  7693   }
  7694.  7694   });
  7695.  7695  
  7696.  7696   // Remove bookmark elements
  7697.  7697   htmlParser.addAttributeFilter('data-mce-type', function(nodes, name, args) {
  7698.  7698   var i = nodes.length, node;
  7699.  7699  
  7700.  7700   while (i--) {
  7701.  7701   node = nodes[i];
  7702.  7702  
  7703.  7703   if (node.attributes.map['data-mce-type'] === 'bookmark' && !args.cleanup)
  7704.  7704   node.remove();
  7705.  7705   }
  7706.  7706   });
  7707.  7707  
  7708.  7708   // Force script into CDATA sections and remove the mce- prefix also add comments around styles
  7709.  7709   htmlParser.addNodeFilter('script,style', function(nodes, name) {
  7710.  7710   var i = nodes.length, node, value;
  7711.  7711  
  7712.  7712   function trim(value) {
  7713.  7713   return value.replace(/(<!--\[CDATA\[|\]\]-->)/g, '\n')
  7714.  7714   .replace(/^[\r\n]*|[\r\n]*$/g, '')
  7715.  7715   .replace(/^\s*(\/\/\s*<!--|\/\/\s*<!\[CDATA\[|<!--|<!\[CDATA\[)[\r\n]*/g, '')
  7716.  7716   .replace(/\s*(\/\/\s*\]\]>|\/\/\s*-->|\]\]>|-->|\]\]-->)\s*$/g, '');
  7717.  7717   };
  7718.  7718  
  7719.  7719   while (i--) {
  7720.  7720   node = nodes[i];
  7721.  7721   value = node.firstChild ? node.firstChild.value : '';
  7722.  7722  
  7723.  7723   if (name === "script") {
  7724.  7724   // Remove mce- prefix from script elements
  7725.  7725   node.attr('type', (node.attr('type') || 'text/javascript').replace(/^mce\-/, ''));
  7726.  7726  
  7727.  7727   if (value.length > 0)
  7728.  7728   node.firstChild.value = '// <![CDATA[\n' + trim(value) + '\n// ]]>';
  7729.  7729   } else {
  7730.  7730   if (value.length > 0)
  7731.  7731   node.firstChild.value = '<!--\n' + trim(value) + '\n-->';
  7732.  7732   }
  7733.  7733   }
  7734.  7734   });
  7735.  7735  
  7736.  7736   // Convert comments to cdata and handle protected comments
  7737.  7737   htmlParser.addNodeFilter('#comment', function(nodes, name) {
  7738.  7738   var i = nodes.length, node;
  7739.  7739  
  7740.  7740   while (i--) {
  7741.  7741   node = nodes[i];
  7742.  7742  
  7743.  7743   if (node.value.indexOf('[CDATA[') === 0) {
  7744.  7744   node.name = '#cdata';
  7745.  7745   node.type = 4;
  7746.  7746   node.value = node.value.replace(/^\[CDATA\[|\]\]$/g, '');
  7747.  7747   } else if (node.value.indexOf('mce:protected ') === 0) {
  7748.  7748   node.name = "#text";
  7749.  7749   node.type = 3;
  7750.  7750   node.raw = true;
  7751.  7751   node.value = unescape(node.value).substr(14);
  7752.  7752   }
  7753.  7753   }
  7754.  7754   });
  7755.  7755  
  7756.  7756   htmlParser.addNodeFilter('xml:namespace,input', function(nodes, name) {
  7757.  7757   var i = nodes.length, node;
  7758.  7758  
  7759.  7759   while (i--) {
  7760.  7760   node = nodes[i];
  7761.  7761   if (node.type === 7)
  7762.  7762   node.remove();
  7763.  7763   else if (node.type === 1) {
  7764.  7764   if (name === "input" && !("type" in node.attributes.map))
  7765.  7765   node.attr('type', 'text');
  7766.  7766   }
  7767.  7767   }
  7768.  7768   });
  7769.  7769  
  7770.  7770   // Fix list elements, TODO: Replace this later
  7771.  7771   if (settings.fix_list_elements) {
  7772.  7772   htmlParser.addNodeFilter('ul,ol', function(nodes, name) {
  7773.  7773   var i = nodes.length, node, parentNode;
  7774.  7774  
  7775.  7775   while (i--) {
  7776.  7776   node = nodes[i];
  7777.  7777   parentNode = node.parent;
  7778.  7778  
  7779.  7779   if (parentNode.name === 'ul' || parentNode.name === 'ol') {
  7780.  7780   if (node.prev && node.prev.name === 'li') {
  7781.  7781   node.prev.append(node);
  7782.  7782   }
  7783.  7783   }
  7784.  7784   }
  7785.  7785   });
  7786.  7786   }
  7787.  7787  
  7788.  7788   // Remove internal data attributes
  7789.  7789   htmlParser.addAttributeFilter('data-mce-src,data-mce-href,data-mce-style', function(nodes, name) {
  7790.  7790   var i = nodes.length;
  7791.  7791  
  7792.  7792   while (i--) {
  7793.  7793   nodes[i].attr(name, null);
  7794.  7794   }
  7795.  7795   });
  7796.  7796  
  7797.  7797   // Return public methods
  7798.  7798   return {
  7799.  7799   schema : schema,
  7800.  7800  
  7801.  7801   addNodeFilter : htmlParser.addNodeFilter,
  7802.  7802  
  7803.  7803   addAttributeFilter : htmlParser.addAttributeFilter,
  7804.  7804  
  7805.  7805   onPreProcess : onPreProcess,
  7806.  7806  
  7807.  7807   onPostProcess : onPostProcess,
  7808.  7808  
  7809.  7809   serialize : function(node, args) {
  7810.  7810   var impl, doc, oldDoc, htmlSerializer, content;
  7811.  7811  
  7812.  7812   // Explorer won't clone contents of script and style and the
  7813.  7813   // selected index of select elements are cleared on a clone operation.
  7814.  7814   if (isIE && dom.select('script,style,select').length > 0) {
  7815.  7815   content = node.innerHTML;
  7816.  7816   node = node.cloneNode(false);
  7817.  7817   dom.setHTML(node, content);
  7818.  7818   } else
  7819.  7819   node = node.cloneNode(true);
  7820.  7820  
  7821.  7821   // Nodes needs to be attached to something in WebKit/Opera
  7822.  7822   // Older builds of Opera crashes if you attach the node to an document created dynamically
  7823.  7823   // and since we can't feature detect a crash we need to sniff the acutal build number
  7824.  7824   // This fix will make DOM ranges and make Sizzle happy!
  7825.  7825   impl = node.ownerDocument.implementation;
  7826.  7826   if (impl.createHTMLDocument) {
  7827.  7827   // Create an empty HTML document
  7828.  7828   doc = impl.createHTMLDocument("");
  7829.  7829  
  7830.  7830   // Add the element or it's children if it's a body element to the new document
  7831.  7831   each(node.nodeName == 'BODY' ? node.childNodes : [node], function(node) {
  7832.  7832   doc.body.appendChild(doc.importNode(node, true));
  7833.  7833   });
  7834.  7834  
  7835.  7835   // Grab first child or body element for serialization
  7836.  7836   if (node.nodeName != 'BODY')
  7837.  7837   node = doc.body.firstChild;
  7838.  7838   else
  7839.  7839   node = doc.body;
  7840.  7840  
  7841.  7841   // set the new document in DOMUtils so createElement etc works
  7842.  7842   oldDoc = dom.doc;
  7843.  7843   dom.doc = doc;
  7844.  7844   }
  7845.  7845  
  7846.  7846   args = args || {};
  7847.  7847   args.format = args.format || 'html';
  7848.  7848  
  7849.  7849   // Pre process
  7850.  7850   if (!args.no_events) {
  7851.  7851   args.node = node;
  7852.  7852   onPreProcess.dispatch(self, args);
  7853.  7853   }
  7854.  7854  
  7855.  7855   // Setup serializer
  7856.  7856   htmlSerializer = new tinymce.html.Serializer(settings, schema);
  7857.  7857  
  7858.  7858   // Parse and serialize HTML
  7859.  7859   args.content = htmlSerializer.serialize(
  7860.  7860   htmlParser.parse(args.getInner ? node.innerHTML : tinymce.trim(dom.getOuterHTML(node), args), args)
  7861.  7861   );
  7862.  7862  
  7863.  7863   // Post process
  7864.  7864   if (!args.no_events)
  7865.  7865   onPostProcess.dispatch(self, args);
  7866.  7866  
  7867.  7867   // Restore the old document if it was changed
  7868.  7868   if (oldDoc)
  7869.  7869   dom.doc = oldDoc;
  7870.  7870  
  7871.  7871   args.node = null;
  7872.  7872  
  7873.  7873   return args.content;
  7874.  7874   },
  7875.  7875  
  7876.  7876   addRules : function(rules) {
  7877.  7877   schema.addValidElements(rules);
  7878.  7878   },
  7879.  7879  
  7880.  7880   setRules : function(rules) {
  7881.  7881   schema.setValidElements(rules);
  7882.  7882   }
  7883.  7883   };
  7884.  7884   };
  7885.  7885  })(tinymce);
  7886.  7886  (function(tinymce) {
  7887.  7887   tinymce.dom.ScriptLoader = function(settings) {
  7888.  7888   var QUEUED = 0,
  7889.  7889   LOADING = 1,
  7890.  7890   LOADED = 2,
  7891.  7891   states = {},
  7892.  7892   queue = [],
  7893.  7893   scriptLoadedCallbacks = {},
  7894.  7894   queueLoadedCallbacks = [],
  7895.  7895   loading = 0,
  7896.  7896   undefined;
  7897.  7897  
  7898.  7898   function loadScript(url, callback) {
  7899.  7899   var t = this, dom = tinymce.DOM, elm, uri, loc, id;
  7900.  7900  
  7901.  7901   // Execute callback when script is loaded
  7902.  7902   function done() {
  7903.  7903   dom.remove(id);
  7904.  7904  
  7905.  7905   if (elm)
  7906.  7906   elm.onreadystatechange = elm.onload = elm = null;
  7907.  7907  
  7908.  7908   callback();
  7909.  7909   };
  7910.  7910  
  7911.  7911   function error() {
  7912.  7912   // Report the error so it's easier for people to spot loading errors
  7913.  7913   if (typeof(console) !== "undefined" && console.log)
  7914.  7914   console.log("Failed to load: " + url);
  7915.  7915  
  7916.  7916   // We can't mark it as done if there is a load error since
  7917.  7917   // A) We don't want to produce 404 errors on the server and
  7918.  7918   // B) the onerror event won't fire on all browsers.
  7919.  7919   // done();
  7920.  7920   };
  7921.  7921  
  7922.  7922   id = dom.uniqueId();
  7923.  7923  
  7924.  7924   if (tinymce.isIE6) {
  7925.  7925   uri = new tinymce.util.URI(url);
  7926.  7926   loc = location;
  7927.  7927  
  7928.  7928   // If script is from same domain and we
  7929.  7929   // use IE 6 then use XHR since it's more reliable
  7930.  7930   if (uri.host == loc.hostname && uri.port == loc.port && (uri.protocol + ':') == loc.protocol && uri.protocol.toLowerCase() != 'file') {
  7931.  7931   tinymce.util.XHR.send({
  7932.  7932   url : tinymce._addVer(uri.getURI()),
  7933.  7933   success : function(content) {
  7934.  7934   // Create new temp script element
  7935.  7935   var script = dom.create('script', {
  7936.  7936   type : 'text/javascript'
  7937.  7937   });
  7938.  7938  
  7939.  7939   // Evaluate script in global scope
  7940.  7940   script.text = content;
  7941.  7941   document.getElementsByTagName('head')[0].appendChild(script);
  7942.  7942   dom.remove(script);
  7943.  7943  
  7944.  7944   done();
  7945.  7945   },
  7946.  7946  
  7947.  7947   error : error
  7948.  7948   });
  7949.  7949  
  7950.  7950   return;
  7951.  7951   }
  7952.  7952   }
  7953.  7953  
  7954.  7954   // Create new script element
  7955.  7955   elm = dom.create('script', {
  7956.  7956   id : id,
  7957.  7957   type : 'text/javascript',
  7958.  7958   src : tinymce._addVer(url)
  7959.  7959   });
  7960.  7960  
  7961.  7961   // Add onload listener for non IE browsers since IE9
  7962.  7962   // fires onload event before the script is parsed and executed
  7963.  7963   if (!tinymce.isIE)
  7964.  7964   elm.onload = done;
  7965.  7965  
  7966.  7966   // Add onerror event will get fired on some browsers but not all of them
  7967.  7967   elm.onerror = error;
  7968.  7968  
  7969.  7969   // Opera 9.60 doesn't seem to fire the onreadystate event at correctly
  7970.  7970   if (!tinymce.isOpera) {
  7971.  7971   elm.onreadystatechange = function() {
  7972.  7972   var state = elm.readyState;
  7973.  7973  
  7974.  7974   // Loaded state is passed on IE 6 however there
  7975.  7975   // are known issues with this method but we can't use
  7976.  7976   // XHR in a cross domain loading
  7977.  7977   if (state == 'complete' || state == 'loaded')
  7978.  7978   done();
  7979.  7979   };
  7980.  7980   }
  7981.  7981  
  7982.  7982   // Most browsers support this feature so we report errors
  7983.  7983   // for those at least to help users track their missing plugins etc
  7984.  7984   // todo: Removed since it produced error if the document is unloaded by navigating away, re-add it as an option
  7985.  7985   /*elm.onerror = function() {
  7986.  7986   alert('Failed to load: ' + url);
  7987.  7987   };*/
  7988.  7988  
  7989.  7989   // Add script to document
  7990.  7990   (document.getElementsByTagName('head')[0] || document.body).appendChild(elm);
  7991.  7991   };
  7992.  7992  
  7993.  7993   this.isDone = function(url) {
  7994.  7994   return states[url] == LOADED;
  7995.  7995   };
  7996.  7996  
  7997.  7997   this.markDone = function(url) {
  7998.  7998   states[url] = LOADED;
  7999.  7999   };
  8000.  8000  
  8001.  8001   this.add = this.load = function(url, callback, scope) {
  8002.  8002   var item, state = states[url];
  8003.  8003  
  8004.  8004   // Add url to load queue
  8005.  8005   if (state == undefined) {
  8006.  8006   queue.push(url);
  8007.  8007   states[url] = QUEUED;
  8008.  8008   }
  8009.  8009  
  8010.  8010   if (callback) {
  8011.  8011   // Store away callback for later execution
  8012.  8012   if (!scriptLoadedCallbacks[url])
  8013.  8013   scriptLoadedCallbacks[url] = [];
  8014.  8014  
  8015.  8015   scriptLoadedCallbacks[url].push({
  8016.  8016   func : callback,
  8017.  8017   scope : scope || this
  8018.  8018   });
  8019.  8019   }
  8020.  8020   };
  8021.  8021  
  8022.  8022   this.loadQueue = function(callback, scope) {
  8023.  8023   this.loadScripts(queue, callback, scope);
  8024.  8024   };
  8025.  8025  
  8026.  8026   this.loadScripts = function(scripts, callback, scope) {
  8027.  8027   var loadScripts;
  8028.  8028  
  8029.  8029   function execScriptLoadedCallbacks(url) {
  8030.  8030   // Execute URL callback functions
  8031.  8031   tinymce.each(scriptLoadedCallbacks[url], function(callback) {
  8032.  8032   callback.func.call(callback.scope);
  8033.  8033   });
  8034.  8034  
  8035.  8035   scriptLoadedCallbacks[url] = undefined;
  8036.  8036   };
  8037.  8037  
  8038.  8038   queueLoadedCallbacks.push({
  8039.  8039   func : callback,
  8040.  8040   scope : scope || this
  8041.  8041   });
  8042.  8042  
  8043.  8043   loadScripts = function() {
  8044.  8044   var loadingScripts = tinymce.grep(scripts);
  8045.  8045  
  8046.  8046   // Current scripts has been handled
  8047.  8047   scripts.length = 0;
  8048.  8048  
  8049.  8049   // Load scripts that needs to be loaded
  8050.  8050   tinymce.each(loadingScripts, function(url) {
  8051.  8051   // Script is already loaded then execute script callbacks directly
  8052.  8052   if (states[url] == LOADED) {
  8053.  8053   execScriptLoadedCallbacks(url);
  8054.  8054   return;
  8055.  8055   }
  8056.  8056  
  8057.  8057   // Is script not loading then start loading it
  8058.  8058   if (states[url] != LOADING) {
  8059.  8059   states[url] = LOADING;
  8060.  8060   loading++;
  8061.  8061  
  8062.  8062   loadScript(url, function() {
  8063.  8063   states[url] = LOADED;
  8064.  8064   loading--;
  8065.  8065  
  8066.  8066   execScriptLoadedCallbacks(url);
  8067.  8067  
  8068.  8068   // Load more scripts if they where added by the recently loaded script
  8069.  8069   loadScripts();
  8070.  8070   });
  8071.  8071   }
  8072.  8072   });
  8073.  8073  
  8074.  8074   // No scripts are currently loading then execute all pending queue loaded callbacks
  8075.  8075   if (!loading) {
  8076.  8076   tinymce.each(queueLoadedCallbacks, function(callback) {
  8077.  8077   callback.func.call(callback.scope);
  8078.  8078   });
  8079.  8079  
  8080.  8080   queueLoadedCallbacks.length = 0;
  8081.  8081   }
  8082.  8082   };
  8083.  8083  
  8084.  8084   loadScripts();
  8085.  8085   };
  8086.  8086   };
  8087.  8087  
  8088.  8088   // Global script loader
  8089.  8089   tinymce.ScriptLoader = new tinymce.dom.ScriptLoader();
  8090.  8090  })(tinymce);
  8091.  8091  
  8092.  8092  tinymce.dom.TreeWalker = function(start_node, root_node) {
  8093.  8093   var node = start_node;
  8094.  8094  
  8095.  8095   function findSibling(node, start_name, sibling_name, shallow) {
  8096.  8096   var sibling, parent;
  8097.  8097  
  8098.  8098   if (node) {
  8099.  8099   // Walk into nodes if it has a start
  8100.  8100   if (!shallow && node[start_name])
  8101.  8101   return node[start_name];
  8102.  8102  
  8103.  8103   // Return the sibling if it has one
  8104.  8104   if (node != root_node) {
  8105.  8105   sibling = node[sibling_name];
  8106.  8106   if (sibling)
  8107.  8107   return sibling;
  8108.  8108  
  8109.  8109   // Walk up the parents to look for siblings
  8110.  8110   for (parent = node.parentNode; parent && parent != root_node; parent = parent.parentNode) {
  8111.  8111   sibling = parent[sibling_name];
  8112.  8112   if (sibling)
  8113.  8113   return sibling;
  8114.  8114   }
  8115.  8115   }
  8116.  8116   }
  8117.  8117   };
  8118.  8118  
  8119.  8119   this.current = function() {
  8120.  8120   return node;
  8121.  8121   };
  8122.  8122  
  8123.  8123   this.next = function(shallow) {
  8124.  8124   return (node = findSibling(node, 'firstChild', 'nextSibling', shallow));
  8125.  8125   };
  8126.  8126  
  8127.  8127   this.prev = function(shallow) {
  8128.  8128   return (node = findSibling(node, 'lastChild', 'previousSibling', shallow));
  8129.  8129   };
  8130.  8130  };
  8131.  8131  
  8132.  8132  (function(tinymce) {
  8133.  8133   tinymce.dom.RangeUtils = function(dom) {
  8134.  8134   var INVISIBLE_CHAR = '\uFEFF';
  8135.  8135  
  8136.  8136   this.walk = function(rng, callback) {
  8137.  8137   var startContainer = rng.startContainer,
  8138.  8138   startOffset = rng.startOffset,
  8139.  8139   endContainer = rng.endContainer,
  8140.  8140   endOffset = rng.endOffset,
  8141.  8141   ancestor, startPoint,
  8142.  8142   endPoint, node, parent, siblings, nodes;
  8143.  8143  
  8144.  8144   // Handle table cell selection the table plugin enables
  8145.  8145   // you to fake select table cells and perform formatting actions on them
  8146.  8146   nodes = dom.select('td.mceSelected,th.mceSelected');
  8147.  8147   if (nodes.length > 0) {
  8148.  8148   tinymce.each(nodes, function(node) {
  8149.  8149   callback([node]);
  8150.  8150   });
  8151.  8151  
  8152.  8152   return;
  8153.  8153   }
  8154.  8154  
  8155.  8155   function collectSiblings(node, name, end_node) {
  8156.  8156   var siblings = [];
  8157.  8157  
  8158.  8158   for (; node && node != end_node; node = node[name])
  8159.  8159   siblings.push(node);
  8160.  8160  
  8161.  8161   return siblings;
  8162.  8162   };
  8163.  8163  
  8164.  8164   function findEndPoint(node, root) {
  8165.  8165   do {
  8166.  8166   if (node.parentNode == root)
  8167.  8167   return node;
  8168.  8168  
  8169.  8169   node = node.parentNode;
  8170.  8170   } while(node);
  8171.  8171   };
  8172.  8172  
  8173.  8173   function walkBoundary(start_node, end_node, next) {
  8174.  8174   var siblingName = next ? 'nextSibling' : 'previousSibling';
  8175.  8175  
  8176.  8176   for (node = start_node, parent = node.parentNode; node && node != end_node; node = parent) {
  8177.  8177   parent = node.parentNode;
  8178.  8178   siblings = collectSiblings(node == start_node ? node : node[siblingName], siblingName);
  8179.  8179  
  8180.  8180   if (siblings.length) {
  8181.  8181   if (!next)
  8182.  8182   siblings.reverse();
  8183.  8183  
  8184.  8184   callback(siblings);
  8185.  8185   }
  8186.  8186   }
  8187.  8187   };
  8188.  8188  
  8189.  8189   // If index based start position then resolve it
  8190.  8190   if (startContainer.nodeType == 1 && startContainer.hasChildNodes())
  8191.  8191   startContainer = startContainer.childNodes[startOffset];
  8192.  8192  
  8193.  8193   // If index based end position then resolve it
  8194.  8194   if (endContainer.nodeType == 1 && endContainer.hasChildNodes())
  8195.  8195   endContainer = endContainer.childNodes[Math.min(endOffset - 1, endContainer.childNodes.length - 1)];
  8196.  8196  
  8197.  8197   // Find common ancestor and end points
  8198.  8198   ancestor = dom.findCommonAncestor(startContainer, endContainer);
  8199.  8199  
  8200.  8200   // Same container
  8201.  8201   if (startContainer == endContainer)
  8202.  8202   return callback([startContainer]);
  8203.  8203  
  8204.  8204   // Process left side
  8205.  8205   for (node = startContainer; node; node = node.parentNode) {
  8206.  8206   if (node == endContainer)
  8207.  8207   return walkBoundary(startContainer, ancestor, true);
  8208.  8208  
  8209.  8209   if (node == ancestor)
  8210.  8210   break;
  8211.  8211   }
  8212.  8212  
  8213.  8213   // Process right side
  8214.  8214   for (node = endContainer; node; node = node.parentNode) {
  8215.  8215   if (node == startContainer)
  8216.  8216   return walkBoundary(endContainer, ancestor);
  8217.  8217  
  8218.  8218   if (node == ancestor)
  8219.  8219   break;
  8220.  8220   }
  8221.  8221  
  8222.  8222   // Find start/end point
  8223.  8223   startPoint = findEndPoint(startContainer, ancestor) || startContainer;
  8224.  8224   endPoint = findEndPoint(endContainer, ancestor) || endContainer;
  8225.  8225  
  8226.  8226   // Walk left leaf
  8227.  8227   walkBoundary(startContainer, startPoint, true);
  8228.  8228  
  8229.  8229   // Walk the middle from start to end point
  8230.  8230   siblings = collectSiblings(
  8231.  8231   startPoint == startContainer ? startPoint : startPoint.nextSibling,
  8232.  8232   'nextSibling',
  8233.  8233   endPoint == endContainer ? endPoint.nextSibling : endPoint
  8234.  8234   );
  8235.  8235  
  8236.  8236   if (siblings.length)
  8237.  8237   callback(siblings);
  8238.  8238  
  8239.  8239   // Walk right leaf
  8240.  8240   walkBoundary(endContainer, endPoint);
  8241.  8241   };
  8242.  8242  
  8243.  8243   /* this.split = function(rng) {
  8244.  8244   var startContainer = rng.startContainer,
  8245.  8245   startOffset = rng.startOffset,
  8246.  8246   endContainer = rng.endContainer,
  8247.  8247   endOffset = rng.endOffset;
  8248.  8248  
  8249.  8249   function splitText(node, offset) {
  8250.  8250   if (offset == node.nodeValue.length)
  8251.  8251   node.appendData(INVISIBLE_CHAR);
  8252.  8252  
  8253.  8253   node = node.splitText(offset);
  8254.  8254  
  8255.  8255   if (node.nodeValue === INVISIBLE_CHAR)
  8256.  8256   node.nodeValue = '';
  8257.  8257  
  8258.  8258   return node;
  8259.  8259   };
  8260.  8260  
  8261.  8261   // Handle single text node
  8262.  8262   if (startContainer == endContainer) {
  8263.  8263   if (startContainer.nodeType == 3) {
  8264.  8264   if (startOffset != 0)
  8265.  8265   startContainer = endContainer = splitText(startContainer, startOffset);
  8266.  8266  
  8267.  8267   if (endOffset - startOffset != startContainer.nodeValue.length)
  8268.  8268   splitText(startContainer, endOffset - startOffset);
  8269.  8269   }
  8270.  8270   } else {
  8271.  8271   // Split startContainer text node if needed
  8272.  8272   if (startContainer.nodeType == 3 && startOffset != 0) {
  8273.  8273   startContainer = splitText(startContainer, startOffset);
  8274.  8274   startOffset = 0;
  8275.  8275   }
  8276.  8276  
  8277.  8277   // Split endContainer text node if needed
  8278.  8278   if (endContainer.nodeType == 3 && endOffset != endContainer.nodeValue.length) {
  8279.  8279   endContainer = splitText(endContainer, endOffset).previousSibling;
  8280.  8280   endOffset = endContainer.nodeValue.length;
  8281.  8281   }
  8282.  8282   }
  8283.  8283  
  8284.  8284   return {
  8285.  8285   startContainer : startContainer,
  8286.  8286   startOffset : startOffset,
  8287.  8287   endContainer : endContainer,
  8288.  8288   endOffset : endOffset
  8289.  8289   };
  8290.  8290   };
  8291.  8291  */
  8292.  8292   };
  8293.  8293  
  8294.  8294   tinymce.dom.RangeUtils.compareRanges = function(rng1, rng2) {
  8295.  8295   if (rng1 && rng2) {
  8296.  8296   // Compare native IE ranges
  8297.  8297   if (rng1.item || rng1.duplicate) {
  8298.  8298   // Both are control ranges and the selected element matches
  8299.  8299   if (rng1.item && rng2.item && rng1.item(0) === rng2.item(0))
  8300.  8300   return true;
  8301.  8301  
  8302.  8302   // Both are text ranges and the range matches
  8303.  8303   if (rng1.isEqual && rng2.isEqual && rng2.isEqual(rng1))
  8304.  8304   return true;
  8305.  8305   } else {
  8306.  8306   // Compare w3c ranges
  8307.  8307   return rng1.startContainer == rng2.startContainer && rng1.startOffset == rng2.startOffset;
  8308.  8308   }
  8309.  8309   }
  8310.  8310  
  8311.  8311   return false;
  8312.  8312   };
  8313.  8313  })(tinymce);
  8314.  8314  
  8315.  8315  (function(tinymce) {
  8316.  8316   var Event = tinymce.dom.Event, each = tinymce.each;
  8317.  8317  
  8318.  8318   tinymce.create('tinymce.ui.KeyboardNavigation', {
  8319.  8319   KeyboardNavigation: function(settings, dom) {
  8320.  8320   var t = this, root = settings.root, items = settings.items,
  8321.  8321   enableUpDown = settings.enableUpDown, enableLeftRight = settings.enableLeftRight || !settings.enableUpDown,
  8322.  8322   excludeFromTabOrder = settings.excludeFromTabOrder,
  8323.  8323   itemFocussed, itemBlurred, rootKeydown, rootFocussed, focussedId;
  8324.  8324  
  8325.  8325   dom = dom || tinymce.DOM;
  8326.  8326  
  8327.  8327   itemFocussed = function(evt) {
  8328.  8328   focussedId = evt.target.id;
  8329.  8329   };
  8330.  8330  
  8331.  8331   itemBlurred = function(evt) {
  8332.  8332   dom.setAttrib(evt.target.id, 'tabindex', '-1');
  8333.  8333   };
  8334.  8334  
  8335.  8335   rootFocussed = function(evt) {
  8336.  8336   var item = dom.get(focussedId);
  8337.  8337   dom.setAttrib(item, 'tabindex', '0');
  8338.  8338   item.focus();
  8339.  8339   };
  8340.  8340  
  8341.  8341   t.focus = function() {
  8342.  8342   dom.get(focussedId).focus();
  8343.  8343   };
  8344.  8344  
  8345.  8345   t.destroy = function() {
  8346.  8346   each(items, function(item) {
  8347.  8347   dom.unbind(dom.get(item.id), 'focus', itemFocussed);
  8348.  8348   dom.unbind(dom.get(item.id), 'blur', itemBlurred);
  8349.  8349   });
  8350.  8350  
  8351.  8351   dom.unbind(dom.get(root), 'focus', rootFocussed);
  8352.  8352   dom.unbind(dom.get(root), 'keydown', rootKeydown);
  8353.  8353  
  8354.  8354   items = dom = root = t.focus = itemFocussed = itemBlurred = rootKeydown = rootFocussed = null;
  8355.  8355   t.destroy = function() {};
  8356.  8356   };
  8357.  8357  
  8358.  8358   t.moveFocus = function(dir, evt) {
  8359.  8359   var idx = -1, controls = t.controls, newFocus;
  8360.  8360  
  8361.  8361   if (!focussedId)
  8362.  8362   return;
  8363.  8363  
  8364.  8364   each(items, function(item, index) {
  8365.  8365   if (item.id === focussedId) {
  8366.  8366   idx = index;
  8367.  8367   return false;
  8368.  8368   }
  8369.  8369   });
  8370.  8370  
  8371.  8371   idx += dir;
  8372.  8372   if (idx < 0) {
  8373.  8373   idx = items.length - 1;
  8374.  8374   } else if (idx >= items.length) {
  8375.  8375   idx = 0;
  8376.  8376   }
  8377.  8377  
  8378.  8378   newFocus = items[idx];
  8379.  8379   dom.setAttrib(focussedId, 'tabindex', '-1');
  8380.  8380   dom.setAttrib(newFocus.id, 'tabindex', '0');
  8381.  8381   dom.get(newFocus.id).focus();
  8382.  8382  
  8383.  8383   if (settings.actOnFocus) {
  8384.  8384   settings.onAction(newFocus.id);
  8385.  8385   }
  8386.  8386  
  8387.  8387   if (evt)
  8388.  8388   Event.cancel(evt);
  8389.  8389   };
  8390.  8390  
  8391.  8391   rootKeydown = function(evt) {
  8392.  8392   var DOM_VK_LEFT = 37, DOM_VK_RIGHT = 39, DOM_VK_UP = 38, DOM_VK_DOWN = 40, DOM_VK_ESCAPE = 27, DOM_VK_ENTER = 14, DOM_VK_RETURN = 13, DOM_VK_SPACE = 32;
  8393.  8393  
  8394.  8394   switch (evt.keyCode) {
  8395.  8395   case DOM_VK_LEFT:
  8396.  8396   if (enableLeftRight) t.moveFocus(-1);
  8397.  8397   break;
  8398.  8398  
  8399.  8399   case DOM_VK_RIGHT:
  8400.  8400   if (enableLeftRight) t.moveFocus(1);
  8401.  8401   break;
  8402.  8402  
  8403.  8403   case DOM_VK_UP:
  8404.  8404   if (enableUpDown) t.moveFocus(-1);
  8405.  8405   break;
  8406.  8406  
  8407.  8407   case DOM_VK_DOWN:
  8408.  8408   if (enableUpDown) t.moveFocus(1);
  8409.  8409   break;
  8410.  8410  
  8411.  8411   case DOM_VK_ESCAPE:
  8412.  8412   if (settings.onCancel) {
  8413.  8413   settings.onCancel();
  8414.  8414   Event.cancel(evt);
  8415.  8415   }
  8416.  8416   break;
  8417.  8417  
  8418.  8418   case DOM_VK_ENTER:
  8419.  8419   case DOM_VK_RETURN:
  8420.  8420   case DOM_VK_SPACE:
  8421.  8421   if (settings.onAction) {
  8422.  8422   settings.onAction(focussedId);
  8423.  8423   Event.cancel(evt);
  8424.  8424   }
  8425.  8425   break;
  8426.  8426   }
  8427.  8427   };
  8428.  8428  
  8429.  8429   // Set up state and listeners for each item.
  8430.  8430   each(items, function(item, idx) {
  8431.  8431   var tabindex;
  8432.  8432  
  8433.  8433   if (!item.id) {
  8434.  8434   item.id = dom.uniqueId('_mce_item_');
  8435.  8435   }
  8436.  8436  
  8437.  8437   if (excludeFromTabOrder) {
  8438.  8438   dom.bind(item.id, 'blur', itemBlurred);
  8439.  8439   tabindex = '-1';
  8440.  8440   } else {
  8441.  8441   tabindex = (idx === 0 ? '0' : '-1');
  8442.  8442   }
  8443.  8443  
  8444.  8444   dom.setAttrib(item.id, 'tabindex', tabindex);
  8445.  8445   dom.bind(dom.get(item.id), 'focus', itemFocussed);
  8446.  8446   });
  8447.  8447  
  8448.  8448   // Setup initial state for root element.
  8449.  8449   if (items[0]){
  8450.  8450   focussedId = items[0].id;
  8451.  8451   }
  8452.  8452  
  8453.  8453   dom.setAttrib(root, 'tabindex', '-1');
  8454.  8454  
  8455.  8455   // Setup listeners for root element.
  8456.  8456   dom.bind(dom.get(root), 'focus', rootFocussed);
  8457.  8457   dom.bind(dom.get(root), 'keydown', rootKeydown);
  8458.  8458   }
  8459.  8459   });
  8460.  8460  })(tinymce);
  8461.  8461  (function(tinymce) {
  8462.  8462   // Shorten class names
  8463.  8463   var DOM = tinymce.DOM, is = tinymce.is;
  8464.  8464  
  8465.  8465   tinymce.create('tinymce.ui.Control', {
  8466.  8466   Control : function(id, s, editor) {
  8467.  8467   this.id = id;
  8468.  8468   this.settings = s = s || {};
  8469.  8469   this.rendered = false;
  8470.  8470   this.onRender = new tinymce.util.Dispatcher(this);
  8471.  8471   this.classPrefix = '';
  8472.  8472   this.scope = s.scope || this;
  8473.  8473   this.disabled = 0;
  8474.  8474   this.active = 0;
  8475.  8475   this.editor = editor;
  8476.  8476   },
  8477.  8477  
  8478.  8478   setAriaProperty : function(property, value) {
  8479.  8479   var element = DOM.get(this.id + '_aria') || DOM.get(this.id);
  8480.  8480   if (element) {
  8481.  8481   DOM.setAttrib(element, 'aria-' + property, !!value);
  8482.  8482   }
  8483.  8483   },
  8484.  8484  
  8485.  8485   focus : function() {
  8486.  8486   DOM.get(this.id).focus();
  8487.  8487   },
  8488.  8488  
  8489.  8489   setDisabled : function(s) {
  8490.  8490   if (s != this.disabled) {
  8491.  8491   this.setAriaProperty('disabled', s);
  8492.  8492  
  8493.  8493   this.setState('Disabled', s);
  8494.  8494   this.setState('Enabled', !s);
  8495.  8495   this.disabled = s;
  8496.  8496   }
  8497.  8497   },
  8498.  8498  
  8499.  8499   isDisabled : function() {
  8500.  8500   return this.disabled;
  8501.  8501   },
  8502.  8502  
  8503.  8503   setActive : function(s) {
  8504.  8504   if (s != this.active) {
  8505.  8505   this.setState('Active', s);
  8506.  8506   this.active = s;
  8507.  8507   this.setAriaProperty('pressed', s);
  8508.  8508   }
  8509.  8509   },
  8510.  8510  
  8511.  8511   isActive : function() {
  8512.  8512   return this.active;
  8513.  8513   },
  8514.  8514  
  8515.  8515   setState : function(c, s) {
  8516.  8516   var n = DOM.get(this.id);
  8517.  8517  
  8518.  8518   c = this.classPrefix + c;
  8519.  8519  
  8520.  8520   if (s)
  8521.  8521   DOM.addClass(n, c);
  8522.  8522   else
  8523.  8523   DOM.removeClass(n, c);
  8524.  8524   },
  8525.  8525  
  8526.  8526   isRendered : function() {
  8527.  8527   return this.rendered;
  8528.  8528   },
  8529.  8529  
  8530.  8530   renderHTML : function() {
  8531.  8531   },
  8532.  8532  
  8533.  8533   renderTo : function(n) {
  8534.  8534   DOM.setHTML(n, this.renderHTML());
  8535.  8535   },
  8536.  8536  
  8537.  8537   postRender : function() {
  8538.  8538   var t = this, b;
  8539.  8539  
  8540.  8540   // Set pending states
  8541.  8541   if (is(t.disabled)) {
  8542.  8542   b = t.disabled;
  8543.  8543   t.disabled = -1;
  8544.  8544   t.setDisabled(b);
  8545.  8545   }
  8546.  8546  
  8547.  8547   if (is(t.active)) {
  8548.  8548   b = t.active;
  8549.  8549   t.active = -1;
  8550.  8550   t.setActive(b);
  8551.  8551   }
  8552.  8552   },
  8553.  8553  
  8554.  8554   remove : function() {
  8555.  8555   DOM.remove(this.id);
  8556.  8556   this.destroy();
  8557.  8557   },
  8558.  8558  
  8559.  8559   destroy : function() {
  8560.  8560   tinymce.dom.Event.clear(this.id);
  8561.  8561   }
  8562.  8562   });
  8563.  8563  })(tinymce);
  8564.  8564  tinymce.create('tinymce.ui.Container:tinymce.ui.Control', {
  8565.  8565   Container : function(id, s, editor) {
  8566.  8566   this.parent(id, s, editor);
  8567.  8567  
  8568.  8568   this.controls = [];
  8569.  8569  
  8570.  8570   this.lookup = {};
  8571.  8571   },
  8572.  8572  
  8573.  8573   add : function(c) {
  8574.  8574   this.lookup[c.id] = c;
  8575.  8575   this.controls.push(c);
  8576.  8576  
  8577.  8577   return c;
  8578.  8578   },
  8579.  8579  
  8580.  8580   get : function(n) {
  8581.  8581   return this.lookup[n];
  8582.  8582   }
  8583.  8583  });
  8584.  8584  
  8585.  8585  
  8586.  8586  tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', {
  8587.  8587   Separator : function(id, s) {
  8588.  8588   this.parent(id, s);
  8589.  8589   this.classPrefix = 'mceSeparator';
  8590.  8590   this.setDisabled(true);
  8591.  8591   },
  8592.  8592  
  8593.  8593   renderHTML : function() {
  8594.  8594   return tinymce.DOM.createHTML('span', {'class' : this.classPrefix, role : 'separator', 'aria-orientation' : 'vertical', tabindex : '-1'});
  8595.  8595   }
  8596.  8596  });
  8597.  8597  
  8598.  8598  (function(tinymce) {
  8599.  8599   var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk;
  8600.  8600  
  8601.  8601   tinymce.create('tinymce.ui.MenuItem:tinymce.ui.Control', {
  8602.  8602   MenuItem : function(id, s) {
  8603.  8603   this.parent(id, s);
  8604.  8604   this.classPrefix = 'mceMenuItem';
  8605.  8605   },
  8606.  8606  
  8607.  8607   setSelected : function(s) {
  8608.  8608   this.setState('Selected', s);
  8609.  8609   this.setAriaProperty('checked', !!s);
  8610.  8610   this.selected = s;
  8611.  8611   },
  8612.  8612  
  8613.  8613   isSelected : function() {
  8614.  8614   return this.selected;
  8615.  8615   },
  8616.  8616  
  8617.  8617   postRender : function() {
  8618.  8618   var t = this;
  8619.  8619  
  8620.  8620   t.parent();
  8621.  8621  
  8622.  8622   // Set pending state
  8623.  8623   if (is(t.selected))
  8624.  8624   t.setSelected(t.selected);
  8625.  8625   }
  8626.  8626   });
  8627.  8627  })(tinymce);
  8628.  8628  
  8629.  8629  (function(tinymce) {
  8630.  8630   var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk;
  8631.  8631  
  8632.  8632   tinymce.create('tinymce.ui.Menu:tinymce.ui.MenuItem', {
  8633.  8633   Menu : function(id, s) {
  8634.  8634   var t = this;
  8635.  8635  
  8636.  8636   t.parent(id, s);
  8637.  8637   t.items = {};
  8638.  8638   t.collapsed = false;
  8639.  8639   t.menuCount = 0;
  8640.  8640   t.onAddItem = new tinymce.util.Dispatcher(this);
  8641.  8641   },
  8642.  8642  
  8643.  8643   expand : function(d) {
  8644.  8644   var t = this;
  8645.  8645  
  8646.  8646   if (d) {
  8647.  8647   walk(t, function(o) {
  8648.  8648   if (o.expand)
  8649.  8649   o.expand();
  8650.  8650   }, 'items', t);
  8651.  8651   }
  8652.  8652  
  8653.  8653   t.collapsed = false;
  8654.  8654   },
  8655.  8655  
  8656.  8656   collapse : function(d) {
  8657.  8657   var t = this;
  8658.  8658  
  8659.  8659   if (d) {
  8660.  8660   walk(t, function(o) {
  8661.  8661   if (o.collapse)
  8662.  8662   o.collapse();
  8663.  8663   }, 'items', t);
  8664.  8664   }
  8665.  8665  
  8666.  8666   t.collapsed = true;
  8667.  8667   },
  8668.  8668  
  8669.  8669   isCollapsed : function() {
  8670.  8670   return this.collapsed;
  8671.  8671   },
  8672.  8672  
  8673.  8673   add : function(o) {
  8674.  8674   if (!o.settings)
  8675.  8675   o = new tinymce.ui.MenuItem(o.id || DOM.uniqueId(), o);
  8676.  8676  
  8677.  8677   this.onAddItem.dispatch(this, o);
  8678.  8678  
  8679.  8679   return this.items[o.id] = o;
  8680.  8680   },
  8681.  8681  
  8682.  8682   addSeparator : function() {
  8683.  8683   return this.add({separator : true});
  8684.  8684   },
  8685.  8685  
  8686.  8686   addMenu : function(o) {
  8687.  8687   if (!o.collapse)
  8688.  8688   o = this.createMenu(o);
  8689.  8689  
  8690.  8690   this.menuCount++;
  8691.  8691  
  8692.  8692   return this.add(o);
  8693.  8693   },
  8694.  8694  
  8695.  8695   hasMenus : function() {
  8696.  8696   return this.menuCount !== 0;
  8697.  8697   },
  8698.  8698  
  8699.  8699   remove : function(o) {
  8700.  8700   delete this.items[o.id];
  8701.  8701   },
  8702.  8702  
  8703.  8703   removeAll : function() {
  8704.  8704   var t = this;
  8705.  8705  
  8706.  8706   walk(t, function(o) {
  8707.  8707   if (o.removeAll)
  8708.  8708   o.removeAll();
  8709.  8709   else
  8710.  8710   o.remove();
  8711.  8711  
  8712.  8712   o.destroy();
  8713.  8713   }, 'items', t);
  8714.  8714  
  8715.  8715   t.items = {};
  8716.  8716   },
  8717.  8717  
  8718.  8718   createMenu : function(o) {
  8719.  8719   var m = new tinymce.ui.Menu(o.id || DOM.uniqueId(), o);
  8720.  8720  
  8721.  8721   m.onAddItem.add(this.onAddItem.dispatch, this.onAddItem);
  8722.  8722  
  8723.  8723   return m;
  8724.  8724   }
  8725.  8725   });
  8726.  8726  })(tinymce);
  8727.  8727  (function(tinymce) {
  8728.  8728   var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, Event = tinymce.dom.Event, Element = tinymce.dom.Element;
  8729.  8729  
  8730.  8730   tinymce.create('tinymce.ui.DropMenu:tinymce.ui.Menu', {
  8731.  8731   DropMenu : function(id, s) {
  8732.  8732   s = s || {};
  8733.  8733   s.container = s.container || DOM.doc.body;
  8734.  8734   s.offset_x = s.offset_x || 0;
  8735.  8735   s.offset_y = s.offset_y || 0;
  8736.  8736   s.vp_offset_x = s.vp_offset_x || 0;
  8737.  8737   s.vp_offset_y = s.vp_offset_y || 0;
  8738.  8738  
  8739.  8739   if (is(s.icons) && !s.icons)
  8740.  8740   s['class'] += ' mceNoIcons';
  8741.  8741  
  8742.  8742   this.parent(id, s);
  8743.  8743   this.onShowMenu = new tinymce.util.Dispatcher(this);
  8744.  8744   this.onHideMenu = new tinymce.util.Dispatcher(this);
  8745.  8745   this.classPrefix = 'mceMenu';
  8746.  8746   },
  8747.  8747  
  8748.  8748   createMenu : function(s) {
  8749.  8749   var t = this, cs = t.settings, m;
  8750.  8750  
  8751.  8751   s.container = s.container || cs.container;
  8752.  8752   s.parent = t;
  8753.  8753   s.constrain = s.constrain || cs.constrain;
  8754.  8754   s['class'] = s['class'] || cs['class'];
  8755.  8755   s.vp_offset_x = s.vp_offset_x || cs.vp_offset_x;
  8756.  8756   s.vp_offset_y = s.vp_offset_y || cs.vp_offset_y;
  8757.  8757   s.keyboard_focus = cs.keyboard_focus;
  8758.  8758   m = new tinymce.ui.DropMenu(s.id || DOM.uniqueId(), s);
  8759.  8759  
  8760.  8760   m.onAddItem.add(t.onAddItem.dispatch, t.onAddItem);
  8761.  8761  
  8762.  8762   return m;
  8763.  8763   },
  8764.  8764  
  8765.  8765   focus : function() {
  8766.  8766   var t = this;
  8767.  8767   if (t.keyboardNav) {
  8768.  8768   t.keyboardNav.focus();
  8769.  8769   }
  8770.  8770   },
  8771.  8771  
  8772.  8772   update : function() {
  8773.  8773   var t = this, s = t.settings, tb = DOM.get('menu_' + t.id + '_tbl'), co = DOM.get('menu_' + t.id + '_co'), tw, th;
  8774.  8774  
  8775.  8775   tw = s.max_width ? Math.min(tb.clientWidth, s.max_width) : tb.clientWidth;
  8776.  8776   th = s.max_height ? Math.min(tb.clientHeight, s.max_height) : tb.clientHeight;
  8777.  8777  
  8778.  8778   if (!DOM.boxModel)
  8779.  8779   t.element.setStyles({width : tw + 2, height : th + 2});
  8780.  8780   else
  8781.  8781   t.element.setStyles({width : tw, height : th});
  8782.  8782  
  8783.  8783   if (s.max_width)
  8784.  8784   DOM.setStyle(co, 'width', tw);
  8785.  8785  
  8786.  8786   if (s.max_height) {
  8787.  8787   DOM.setStyle(co, 'height', th);
  8788.  8788  
  8789.  8789   if (tb.clientHeight < s.max_height)
  8790.  8790   DOM.setStyle(co, 'overflow', 'hidden');
  8791.  8791   }
  8792.  8792   },
  8793.  8793  
  8794.  8794   showMenu : function(x, y, px) {
  8795.  8795   var t = this, s = t.settings, co, vp = DOM.getViewPort(), w, h, mx, my, ot = 2, dm, tb, cp = t.classPrefix;
  8796.  8796  
  8797.  8797   t.collapse(1);
  8798.  8798  
  8799.  8799   if (t.isMenuVisible)
  8800.  8800   return;
  8801.  8801  
  8802.  8802   if (!t.rendered) {
  8803.  8803   co = DOM.add(t.settings.container, t.renderNode());
  8804.  8804  
  8805.  8805   each(t.items, function(o) {
  8806.  8806   o.postRender();
  8807.  8807   });
  8808.  8808  
  8809.  8809   t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container});
  8810.  8810   } else
  8811.  8811   co = DOM.get('menu_' + t.id);
  8812.  8812  
  8813.  8813   // Move layer out of sight unless it's Opera since it scrolls to top of page due to an bug
  8814.  8814   if (!tinymce.isOpera)
  8815.  8815   DOM.setStyles(co, {left : -0xFFFF , top : -0xFFFF});
  8816.  8816  
  8817.  8817   DOM.show(co);
  8818.  8818   t.update();
  8819.  8819  
  8820.  8820   x += s.offset_x || 0;
  8821.  8821   y += s.offset_y || 0;
  8822.  8822   vp.w -= 4;
  8823.  8823   vp.h -= 4;
  8824.  8824  
  8825.  8825   // Move inside viewport if not submenu
  8826.  8826   if (s.constrain) {
  8827.  8827   w = co.clientWidth - ot;
  8828.  8828   h = co.clientHeight - ot;
  8829.  8829   mx = vp.x + vp.w;
  8830.  8830   my = vp.y + vp.h;
  8831.  8831  
  8832.  8832   if ((x + s.vp_offset_x + w) > mx)
  8833.  8833   x = px ? px - w : Math.max(0, (mx - s.vp_offset_x) - w);
  8834.  8834  
  8835.  8835   if ((y + s.vp_offset_y + h) > my)
  8836.  8836   y = Math.max(0, (my - s.vp_offset_y) - h);
  8837.  8837   }
  8838.  8838  
  8839.  8839   DOM.setStyles(co, {left : x , top : y});
  8840.  8840   t.element.update();
  8841.  8841  
  8842.  8842   t.isMenuVisible = 1;
  8843.  8843   t.mouseClickFunc = Event.add(co, 'click', function(e) {
  8844.  8844   var m;
  8845.  8845  
  8846.  8846   e = e.target;
  8847.  8847  
  8848.  8848   if (e && (e = DOM.getParent(e, 'tr')) && !DOM.hasClass(e, cp + 'ItemSub')) {
  8849.  8849   m = t.items[e.id];
  8850.  8850  
  8851.  8851   if (m.isDisabled())
  8852.  8852   return;
  8853.  8853  
  8854.  8854   dm = t;
  8855.  8855  
  8856.  8856   while (dm) {
  8857.  8857   if (dm.hideMenu)
  8858.  8858   dm.hideMenu();
  8859.  8859  
  8860.  8860   dm = dm.settings.parent;
  8861.  8861   }
  8862.  8862  
  8863.  8863   if (m.settings.onclick)
  8864.  8864   m.settings.onclick(e);
  8865.  8865  
  8866.  8866   return Event.cancel(e); // Cancel to fix onbeforeunload problem
  8867.  8867   }
  8868.  8868   });
  8869.  8869  
  8870.  8870   if (t.hasMenus()) {
  8871.  8871   t.mouseOverFunc = Event.add(co, 'mouseover', function(e) {
  8872.  8872   var m, r, mi;
  8873.  8873  
  8874.  8874   e = e.target;
  8875.  8875   if (e && (e = DOM.getParent(e, 'tr'))) {
  8876.  8876   m = t.items[e.id];
  8877.  8877  
  8878.  8878   if (t.lastMenu)
  8879.  8879   t.lastMenu.collapse(1);
  8880.  8880  
  8881.  8881   if (m.isDisabled())
  8882.  8882   return;
  8883.  8883  
  8884.  8884   if (e && DOM.hasClass(e, cp + 'ItemSub')) {
  8885.  8885   //p = DOM.getPos(s.container);
  8886.  8886   r = DOM.getRect(e);
  8887.  8887   m.showMenu((r.x + r.w - ot), r.y - ot, r.x);
  8888.  8888   t.lastMenu = m;
  8889.  8889   DOM.addClass(DOM.get(m.id).firstChild, cp + 'ItemActive');
  8890.  8890   }
  8891.  8891   }
  8892.  8892   });
  8893.  8893   }
  8894.  8894  
  8895.  8895   Event.add(co, 'keydown', t._keyHandler, t);
  8896.  8896  
  8897.  8897   t.onShowMenu.dispatch(t);
  8898.  8898  
  8899.  8899   if (s.keyboard_focus) {
  8900.  8900   t._setupKeyboardNav();
  8901.  8901   }
  8902.  8902   },
  8903.  8903  
  8904.  8904   hideMenu : function(c) {
  8905.  8905   var t = this, co = DOM.get('menu_' + t.id), e;
  8906.  8906  
  8907.  8907   if (!t.isMenuVisible)
  8908.  8908   return;
  8909.  8909  
  8910.  8910   if (t.keyboardNav) t.keyboardNav.destroy();
  8911.  8911   Event.remove(co, 'mouseover', t.mouseOverFunc);
  8912.  8912   Event.remove(co, 'click', t.mouseClickFunc);
  8913.  8913   Event.remove(co, 'keydown', t._keyHandler);
  8914.  8914   DOM.hide(co);
  8915.  8915   t.isMenuVisible = 0;
  8916.  8916  
  8917.  8917   if (!c)
  8918.  8918   t.collapse(1);
  8919.  8919  
  8920.  8920   if (t.element)
  8921.  8921   t.element.hide();
  8922.  8922  
  8923.  8923   if (e = DOM.get(t.id))
  8924.  8924   DOM.removeClass(e.firstChild, t.classPrefix + 'ItemActive');
  8925.  8925  
  8926.  8926   t.onHideMenu.dispatch(t);
  8927.  8927   },
  8928.  8928  
  8929.  8929   add : function(o) {
  8930.  8930   var t = this, co;
  8931.  8931  
  8932.  8932   o = t.parent(o);
  8933.  8933  
  8934.  8934   if (t.isRendered && (co = DOM.get('menu_' + t.id)))
  8935.  8935   t._add(DOM.select('tbody', co)[0], o);
  8936.  8936  
  8937.  8937   return o;
  8938.  8938   },
  8939.  8939  
  8940.  8940   collapse : function(d) {
  8941.  8941   this.parent(d);
  8942.  8942   this.hideMenu(1);
  8943.  8943   },
  8944.  8944  
  8945.  8945   remove : function(o) {
  8946.  8946   DOM.remove(o.id);
  8947.  8947   this.destroy();
  8948.  8948  
  8949.  8949   return this.parent(o);
  8950.  8950   },
  8951.  8951  
  8952.  8952   destroy : function() {
  8953.  8953   var t = this, co = DOM.get('menu_' + t.id);
  8954.  8954  
  8955.  8955   if (t.keyboardNav) t.keyboardNav.destroy();
  8956.  8956   Event.remove(co, 'mouseover', t.mouseOverFunc);
  8957.  8957   Event.remove(DOM.select('a', co), 'focus', t.mouseOverFunc);
  8958.  8958   Event.remove(co, 'click', t.mouseClickFunc);
  8959.  8959   Event.remove(co, 'keydown', t._keyHandler);
  8960.  8960  
  8961.  8961   if (t.element)
  8962.  8962   t.element.remove();
  8963.  8963  
  8964.  8964   DOM.remove(co);
  8965.  8965   },
  8966.  8966  
  8967.  8967   renderNode : function() {
  8968.  8968   var t = this, s = t.settings, n, tb, co, w;
  8969.  8969  
  8970.  8970   w = DOM.create('div', {role: 'listbox', id : 'menu_' + t.id, 'class' : s['class'], 'style' : 'position:absolute;left:0;top:0;z-index:200000;outline:0'});
  8971.  8971   if (t.settings.parent) {
  8972.  8972   DOM.setAttrib(w, 'aria-parent', 'menu_' + t.settings.parent.id);
  8973.  8973   }
  8974.  8974   co = DOM.add(w, 'div', {role: 'presentation', id : 'menu_' + t.id + '_co', 'class' : t.classPrefix + (s['class'] ? ' ' + s['class'] : '')});
  8975.  8975   t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container});
  8976.  8976  
  8977.  8977   if (s.menu_line)
  8978.  8978   DOM.add(co, 'span', {'class' : t.classPrefix + 'Line'});
  8979.  8979  
  8980.  8980  // n = DOM.add(co, 'div', {id : 'menu_' + t.id + '_co', 'class' : 'mceMenuContainer'});
  8981.  8981   n = DOM.add(co, 'table', {role: 'presentation', id : 'menu_' + t.id + '_tbl', border : 0, cellPadding : 0, cellSpacing : 0});
  8982.  8982   tb = DOM.add(n, 'tbody');
  8983.  8983  
  8984.  8984   each(t.items, function(o) {
  8985.  8985   t._add(tb, o);
  8986.  8986   });
  8987.  8987  
  8988.  8988   t.rendered = true;
  8989.  8989  
  8990.  8990   return w;
  8991.  8991   },
  8992.  8992  
  8993.  8993   // Internal functions
  8994.  8994   _setupKeyboardNav : function(){
  8995.  8995   var contextMenu, menuItems, t=this;
  8996.  8996   contextMenu = DOM.select('#menu_' + t.id)[0];
  8997.  8997   menuItems = DOM.select('a[role=option]', 'menu_' + t.id);
  8998.  8998   menuItems.splice(0,0,contextMenu);
  8999.  8999   t.keyboardNav = new tinymce.ui.KeyboardNavigation({
  9000.  9000   root: 'menu_' + t.id,
  9001.  9001   items: menuItems,
  9002.  9002   onCancel: function() {
  9003.  9003   t.hideMenu();
  9004.  9004   },
  9005.  9005   enableUpDown: true
  9006.  9006   });
  9007.  9007   contextMenu.focus();
  9008.  9008   },
  9009.  9009  
  9010.  9010   _keyHandler : function(evt) {
  9011.  9011   var t = this, e;
  9012.  9012   switch (evt.keyCode) {
  9013.  9013   case 37: // Left
  9014.  9014   if (t.settings.parent) {
  9015.  9015   t.hideMenu();
  9016.  9016   t.settings.parent.focus();
  9017.  9017   Event.cancel(evt);
  9018.  9018   }
  9019.  9019   break;
  9020.  9020   case 39: // Right
  9021.  9021   if (t.mouseOverFunc)
  9022.  9022   t.mouseOverFunc(evt);
  9023.  9023   break;
  9024.  9024   }
  9025.  9025   },
  9026.  9026  
  9027.  9027   _add : function(tb, o) {
  9028.  9028   var n, s = o.settings, a, ro, it, cp = this.classPrefix, ic;
  9029.  9029  
  9030.  9030   if (s.separator) {
  9031.  9031   ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'ItemSeparator'});
  9032.  9032   DOM.add(ro, 'td', {'class' : cp + 'ItemSeparator'});
  9033.  9033  
  9034.  9034   if (n = ro.previousSibling)
  9035.  9035   DOM.addClass(n, 'mceLast');
  9036.  9036  
  9037.  9037   return;
  9038.  9038   }
  9039.  9039  
  9040.  9040   n = ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'Item ' + cp + 'ItemEnabled'});
  9041.  9041   n = it = DOM.add(n, s.titleItem ? 'th' : 'td');
  9042.  9042   n = a = DOM.add(n, 'a', {id: o.id + '_aria', role: s.titleItem ? 'presentation' : 'option', href : 'javascript:;', onclick : "return false;", onmousedown : 'return false;'});
  9043.  9043  
  9044.  9044   if (s.parent) {
  9045.  9045   DOM.setAttrib(a, 'aria-haspopup', 'true');
  9046.  9046   DOM.setAttrib(a, 'aria-owns', 'menu_' + o.id);
  9047.  9047   }
  9048.  9048  
  9049.  9049   DOM.addClass(it, s['class']);
  9050.  9050  // n = DOM.add(n, 'span', {'class' : 'item'});
  9051.  9051  
  9052.  9052   ic = DOM.add(n, 'span', {'class' : 'mceIcon' + (s.icon ? ' mce_' + s.icon : '')});
  9053.  9053  
  9054.  9054   if (s.icon_src)
  9055.  9055   DOM.add(ic, 'img', {src : s.icon_src});
  9056.  9056  
  9057.  9057   n = DOM.add(n, s.element || 'span', {'class' : 'mceText', title : o.settings.title}, o.settings.title);
  9058.  9058  
  9059.  9059   if (o.settings.style)
  9060.  9060   DOM.setAttrib(n, 'style', o.settings.style);
  9061.  9061  
  9062.  9062   if (tb.childNodes.length == 1)
  9063.  9063   DOM.addClass(ro, 'mceFirst');
  9064.  9064  
  9065.  9065   if ((n = ro.previousSibling) && DOM.hasClass(n, cp + 'ItemSeparator'))
  9066.  9066   DOM.addClass(ro, 'mceFirst');
  9067.  9067  
  9068.  9068   if (o.collapse)
  9069.  9069   DOM.addClass(ro, cp + 'ItemSub');
  9070.  9070  
  9071.  9071   if (n = ro.previousSibling)
  9072.  9072   DOM.removeClass(n, 'mceLast');
  9073.  9073  
  9074.  9074   DOM.addClass(ro, 'mceLast');
  9075.  9075   }
  9076.  9076   });
  9077.  9077  })(tinymce);
  9078.  9078  (function(tinymce) {
  9079.  9079   var DOM = tinymce.DOM;
  9080.  9080  
  9081.  9081   tinymce.create('tinymce.ui.Button:tinymce.ui.Control', {
  9082.  9082   Button : function(id, s, ed) {
  9083.  9083   this.parent(id, s, ed);
  9084.  9084   this.classPrefix = 'mceButton';
  9085.  9085   },
  9086.  9086  
  9087.  9087   renderHTML : function() {
  9088.  9088   var cp = this.classPrefix, s = this.settings, h, l;
  9089.  9089  
  9090.  9090   l = DOM.encode(s.label || '');
  9091.  9091   h = '<a role="button" id="' + this.id + '" href="javascript:;" class="' + cp + ' ' + cp + 'Enabled ' + s['class'] + (l ? ' ' + cp + 'Labeled' : '') +'" onmousedown="return false;" onclick="return false;" aria-labelledby="' + this.id + '_voice" title="' + DOM.encode(s.title) + '">';
  9092.  9092  
  9093.  9093   if (s.image)
  9094.  9094   h += '<img class="mceIcon" src="' + s.image + '" alt="' + DOM.encode(s.title) + '" />' + l;
  9095.  9095   else
  9096.  9096   h += '<span class="mceIcon ' + s['class'] + '"></span>' + (l ? '<span class="' + cp + 'Label">' + l + '</span>' : '');
  9097.  9097  
  9098.  9098   h += '<span class="mceVoiceLabel mceIconOnly" style="display: none;" id="' + this.id + '_voice">' + s.title + '</span>';
  9099.  9099   h += '</a>';
  9100.  9100   return h;
  9101.  9101   },
  9102.  9102  
  9103.  9103   postRender : function() {
  9104.  9104   var t = this, s = t.settings;
  9105.  9105  
  9106.  9106   tinymce.dom.Event.add(t.id, 'click', function(e) {
  9107.  9107   if (!t.isDisabled())
  9108.  9108   return s.onclick.call(s.scope, e);
  9109.  9109   });
  9110.  9110   }
  9111.  9111   });
  9112.  9112  })(tinymce);
  9113.  9113  
  9114.  9114  (function(tinymce) {
  9115.  9115   var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher;
  9116.  9116  
  9117.  9117   tinymce.create('tinymce.ui.ListBox:tinymce.ui.Control', {
  9118.  9118   ListBox : function(id, s, ed) {
  9119.  9119   var t = this;
  9120.  9120  
  9121.  9121   t.parent(id, s, ed);
  9122.  9122  
  9123.  9123   t.items = [];
  9124.  9124  
  9125.  9125   t.onChange = new Dispatcher(t);
  9126.  9126  
  9127.  9127   t.onPostRender = new Dispatcher(t);
  9128.  9128  
  9129.  9129   t.onAdd = new Dispatcher(t);
  9130.  9130  
  9131.  9131   t.onRenderMenu = new tinymce.util.Dispatcher(this);
  9132.  9132  
  9133.  9133   t.classPrefix = 'mceListBox';
  9134.  9134   },
  9135.  9135  
  9136.  9136   select : function(va) {
  9137.  9137   var t = this, fv, f;
  9138.  9138  
  9139.  9139   if (va == undefined)
  9140.  9140   return t.selectByIndex(-1);
  9141.  9141  
  9142.  9142   // Is string or number make function selector
  9143.  9143   if (va && va.call)
  9144.  9144   f = va;
  9145.  9145   else {
  9146.  9146   f = function(v) {
  9147.  9147   return v == va;
  9148.  9148   };
  9149.  9149   }
  9150.  9150  
  9151.  9151   // Do we need to do something?
  9152.  9152   if (va != t.selectedValue) {
  9153.  9153   // Find item
  9154.  9154   each(t.items, function(o, i) {
  9155.  9155   if (f(o.value)) {
  9156.  9156   fv = 1;
  9157.  9157   t.selectByIndex(i);
  9158.  9158   return false;
  9159.  9159   }
  9160.  9160   });
  9161.  9161  
  9162.  9162   if (!fv)
  9163.  9163   t.selectByIndex(-1);
  9164.  9164   }
  9165.  9165   },
  9166.  9166  
  9167.  9167   selectByIndex : function(idx) {
  9168.  9168   var t = this, e, o;
  9169.  9169  
  9170.  9170   if (idx != t.selectedIndex) {
  9171.  9171   e = DOM.get(t.id + '_text');
  9172.  9172   o = t.items[idx];
  9173.  9173  
  9174.  9174   if (o) {
  9175.  9175   t.selectedValue = o.value;
  9176.  9176   t.selectedIndex = idx;
  9177.  9177   DOM.setHTML(e, DOM.encode(o.title));
  9178.  9178   DOM.removeClass(e, 'mceTitle');
  9179.  9179   DOM.setAttrib(t.id, 'aria-valuenow', o.title);
  9180.  9180   } else {
  9181.  9181   DOM.setHTML(e, DOM.encode(t.settings.title));
  9182.  9182   DOM.addClass(e, 'mceTitle');
  9183.  9183   t.selectedValue = t.selectedIndex = null;
  9184.  9184   DOM.setAttrib(t.id, 'aria-valuenow', t.settings.title);
  9185.  9185   }
  9186.  9186   e = 0;
  9187.  9187   }
  9188.  9188   },
  9189.  9189  
  9190.  9190   add : function(n, v, o) {
  9191.  9191   var t = this;
  9192.  9192  
  9193.  9193   o = o || {};
  9194.  9194   o = tinymce.extend(o, {
  9195.  9195   title : n,
  9196.  9196   value : v
  9197.  9197   });
  9198.  9198  
  9199.  9199   t.items.push(o);
  9200.  9200   t.onAdd.dispatch(t, o);
  9201.  9201   },
  9202.  9202  
  9203.  9203   getLength : function() {
  9204.  9204   return this.items.length;
  9205.  9205   },
  9206.  9206  
  9207.  9207   renderHTML : function() {
  9208.  9208   var h = '', t = this, s = t.settings, cp = t.classPrefix;
  9209.  9209  
  9210.  9210   h = '<span role="button" aria-haspopup="true" aria-labelledby="' + t.id +'_text" aria-describedby="' + t.id + '_voiceDesc"><table role="presentation" tabindex="0" id="' + t.id + '" cellpadding="0" cellspacing="0" class="' + cp + ' ' + cp + 'Enabled' + (s['class'] ? (' ' + s['class']) : '') + '"><tbody><tr>';
  9211.  9211   h += '<td>' + DOM.createHTML('span', {id: t.id + '_voiceDesc', 'class': 'voiceLabel', style:'display:none;'}, t.settings.title);
  9212.  9212   h += DOM.createHTML('a', {id : t.id + '_text', tabindex : -1, href : 'javascript:;', 'class' : 'mceText', onclick : "return false;", onmousedown : 'return false;'}, DOM.encode(t.settings.title)) + '</td>';
  9213.  9213   h += '<td>' + DOM.createHTML('a', {id : t.id + '_open', tabindex : -1, href : 'javascript:;', 'class' : 'mceOpen', onclick : "return false;", onmousedown : 'return false;'}, '<span><span style="display:none;" class="mceIconOnly" aria-hidden="true">\u25BC</span></span>') + '</td>';
  9214.  9214   h += '</tr></tbody></table></span>';
  9215.  9215  
  9216.  9216   return h;
  9217.  9217   },
  9218.  9218  
  9219.  9219   showMenu : function() {
  9220.  9220   var t = this, p1, p2, e = DOM.get(this.id), m;
  9221.  9221  
  9222.  9222   if (t.isDisabled() || t.items.length == 0)
  9223.  9223   return;
  9224.  9224  
  9225.  9225   if (t.menu && t.menu.isMenuVisible)
  9226.  9226   return t.hideMenu();
  9227.  9227  
  9228.  9228   if (!t.isMenuRendered) {
  9229.  9229   t.renderMenu();
  9230.  9230   t.isMenuRendered = true;
  9231.  9231   }
  9232.  9232  
  9233.  9233   p1 = DOM.getPos(this.settings.menu_container);
  9234.  9234   p2 = DOM.getPos(e);
  9235.  9235  
  9236.  9236   m = t.menu;
  9237.  9237   m.settings.offset_x = p2.x;
  9238.  9238   m.settings.offset_y = p2.y;
  9239.  9239   m.settings.keyboard_focus = !tinymce.isOpera; // Opera is buggy when it comes to auto focus
  9240.  9240  
  9241.  9241   // Select in menu
  9242.  9242   if (t.oldID)
  9243.  9243   m.items[t.oldID].setSelected(0);
  9244.  9244  
  9245.  9245   each(t.items, function(o) {
  9246.  9246   if (o.value === t.selectedValue) {
  9247.  9247   m.items[o.id].setSelected(1);
  9248.  9248   t.oldID = o.id;
  9249.  9249   }
  9250.  9250   });
  9251.  9251  
  9252.  9252   m.showMenu(0, e.clientHeight);
  9253.  9253  
  9254.  9254   Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
  9255.  9255   DOM.addClass(t.id, t.classPrefix + 'Selected');
  9256.  9256  
  9257.  9257   //DOM.get(t.id + '_text').focus();
  9258.  9258   },
  9259.  9259  
  9260.  9260   hideMenu : function(e) {
  9261.  9261   var t = this;
  9262.  9262  
  9263.  9263   if (t.menu && t.menu.isMenuVisible) {
  9264.  9264   DOM.removeClass(t.id, t.classPrefix + 'Selected');
  9265.  9265  
  9266.  9266   // Prevent double toogles by canceling the mouse click event to the button
  9267.  9267   if (e && e.type == "mousedown" && (e.target.id == t.id + '_text' || e.target.id == t.id + '_open'))
  9268.  9268   return;
  9269.  9269  
  9270.  9270   if (!e || !DOM.getParent(e.target, '.mceMenu')) {
  9271.  9271   DOM.removeClass(t.id, t.classPrefix + 'Selected');
  9272.  9272   Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
  9273.  9273   t.menu.hideMenu();
  9274.  9274   }
  9275.  9275   }
  9276.  9276   },
  9277.  9277  
  9278.  9278   renderMenu : function() {
  9279.  9279   var t = this, m;
  9280.  9280  
  9281.  9281   m = t.settings.control_manager.createDropMenu(t.id + '_menu', {
  9282.  9282   menu_line : 1,
  9283.  9283   'class' : t.classPrefix + 'Menu mceNoIcons',
  9284.  9284   max_width : 150,
  9285.  9285   max_height : 150
  9286.  9286   });
  9287.  9287  
  9288.  9288   m.onHideMenu.add(function() {
  9289.  9289   t.hideMenu();
  9290.  9290   t.focus();
  9291.  9291   });
  9292.  9292  
  9293.  9293   m.add({
  9294.  9294   title : t.settings.title,
  9295.  9295   'class' : 'mceMenuItemTitle',
  9296.  9296   onclick : function() {
  9297.  9297   if (t.settings.onselect('') !== false)
  9298.  9298   t.select(''); // Must be runned after
  9299.  9299   }
  9300.  9300   });
  9301.  9301  
  9302.  9302   each(t.items, function(o) {
  9303.  9303   // No value then treat it as a title
  9304.  9304   if (o.value === undefined) {
  9305.  9305   m.add({
  9306.  9306   title : o.title,
  9307.  9307   'class' : 'mceMenuItemTitle',
  9308.  9308   onclick : function() {
  9309.  9309   if (t.settings.onselect('') !== false)
  9310.  9310   t.select(''); // Must be runned after
  9311.  9311   }
  9312.  9312   });
  9313.  9313   } else {
  9314.  9314   o.id = DOM.uniqueId();
  9315.  9315   o.onclick = function() {
  9316.  9316   if (t.settings.onselect(o.value) !== false)
  9317.  9317   t.select(o.value); // Must be runned after
  9318.  9318   };
  9319.  9319  
  9320.  9320   m.add(o);
  9321.  9321   }
  9322.  9322   });
  9323.  9323  
  9324.  9324   t.onRenderMenu.dispatch(t, m);
  9325.  9325   t.menu = m;
  9326.  9326   },
  9327.  9327  
  9328.  9328   postRender : function() {
  9329.  9329   var t = this, cp = t.classPrefix;
  9330.  9330  
  9331.  9331   Event.add(t.id, 'click', t.showMenu, t);
  9332.  9332   Event.add(t.id, 'keydown', function(evt) {
  9333.  9333   if (evt.keyCode == 32) { // Space
  9334.  9334   t.showMenu(evt);
  9335.  9335   Event.cancel(evt);
  9336.  9336   }
  9337.  9337   });
  9338.  9338   Event.add(t.id, 'focus', function() {
  9339.  9339   if (!t._focused) {
  9340.  9340   t.keyDownHandler = Event.add(t.id, 'keydown', function(e) {
  9341.  9341   if (e.keyCode == 40) {
  9342.  9342   t.showMenu();
  9343.  9343   Event.cancel(e);
  9344.  9344   }
  9345.  9345   });
  9346.  9346   t.keyPressHandler = Event.add(t.id, 'keypress', function(e) {
  9347.  9347   var v;
  9348.  9348   if (e.keyCode == 13) {
  9349.  9349   // Fake select on enter
  9350.  9350   v = t.selectedValue;
  9351.  9351   t.selectedValue = null; // Needs to be null to fake change
  9352.  9352   Event.cancel(e);
  9353.  9353   t.settings.onselect(v);
  9354.  9354   }
  9355.  9355   });
  9356.  9356   }
  9357.  9357  
  9358.  9358   t._focused = 1;
  9359.  9359   });
  9360.  9360   Event.add(t.id, 'blur', function() {
  9361.  9361   Event.remove(t.id, 'keydown', t.keyDownHandler);
  9362.  9362   Event.remove(t.id, 'keypress', t.keyPressHandler);
  9363.  9363   t._focused = 0;
  9364.  9364   });
  9365.  9365  
  9366.  9366   // Old IE doesn't have hover on all elements
  9367.  9367   if (tinymce.isIE6 || !DOM.boxModel) {
  9368.  9368   Event.add(t.id, 'mouseover', function() {
  9369.  9369   if (!DOM.hasClass(t.id, cp + 'Disabled'))
  9370.  9370   DOM.addClass(t.id, cp + 'Hover');
  9371.  9371   });
  9372.  9372  
  9373.  9373   Event.add(t.id, 'mouseout', function() {
  9374.  9374   if (!DOM.hasClass(t.id, cp + 'Disabled'))
  9375.  9375   DOM.removeClass(t.id, cp + 'Hover');
  9376.  9376   });
  9377.  9377   }
  9378.  9378  
  9379.  9379   t.onPostRender.dispatch(t, DOM.get(t.id));
  9380.  9380   },
  9381.  9381  
  9382.  9382   destroy : function() {
  9383.  9383   this.parent();
  9384.  9384  
  9385.  9385   Event.clear(this.id + '_text');
  9386.  9386   Event.clear(this.id + '_open');
  9387.  9387   }
  9388.  9388   });
  9389.  9389  })(tinymce);
  9390.  9390  (function(tinymce) {
  9391.  9391   var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher;
  9392.  9392  
  9393.  9393   tinymce.create('tinymce.ui.NativeListBox:tinymce.ui.ListBox', {
  9394.  9394   NativeListBox : function(id, s) {
  9395.  9395   this.parent(id, s);
  9396.  9396   this.classPrefix = 'mceNativeListBox';
  9397.  9397   },
  9398.  9398  
  9399.  9399   setDisabled : function(s) {
  9400.  9400   DOM.get(this.id).disabled = s;
  9401.  9401   this.setAriaProperty('disabled', s);
  9402.  9402   },
  9403.  9403  
  9404.  9404   isDisabled : function() {
  9405.  9405   return DOM.get(this.id).disabled;
  9406.  9406   },
  9407.  9407  
  9408.  9408   select : function(va) {
  9409.  9409   var t = this, fv, f;
  9410.  9410  
  9411.  9411   if (va == undefined)
  9412.  9412   return t.selectByIndex(-1);
  9413.  9413  
  9414.  9414   // Is string or number make function selector
  9415.  9415   if (va && va.call)
  9416.  9416   f = va;
  9417.  9417   else {
  9418.  9418   f = function(v) {
  9419.  9419   return v == va;
  9420.  9420   };
  9421.  9421   }
  9422.  9422  
  9423.  9423   // Do we need to do something?
  9424.  9424   if (va != t.selectedValue) {
  9425.  9425   // Find item
  9426.  9426   each(t.items, function(o, i) {
  9427.  9427   if (f(o.value)) {
  9428.  9428   fv = 1;
  9429.  9429   t.selectByIndex(i);
  9430.  9430   return false;
  9431.  9431   }
  9432.  9432   });
  9433.  9433  
  9434.  9434   if (!fv)
  9435.  9435   t.selectByIndex(-1);
  9436.  9436   }
  9437.  9437   },
  9438.  9438  
  9439.  9439   selectByIndex : function(idx) {
  9440.  9440   DOM.get(this.id).selectedIndex = idx + 1;
  9441.  9441   this.selectedValue = this.items[idx] ? this.items[idx].value : null;
  9442.  9442   },
  9443.  9443  
  9444.  9444   add : function(n, v, a) {
  9445.  9445   var o, t = this;
  9446.  9446  
  9447.  9447   a = a || {};
  9448.  9448   a.value = v;
  9449.  9449  
  9450.  9450   if (t.isRendered())
  9451.  9451   DOM.add(DOM.get(this.id), 'option', a, n);
  9452.  9452  
  9453.  9453   o = {
  9454.  9454   title : n,
  9455.  9455   value : v,
  9456.  9456   attribs : a
  9457.  9457   };
  9458.  9458  
  9459.  9459   t.items.push(o);
  9460.  9460   t.onAdd.dispatch(t, o);
  9461.  9461   },
  9462.  9462  
  9463.  9463   getLength : function() {
  9464.  9464   return this.items.length;
  9465.  9465   },
  9466.  9466  
  9467.  9467   renderHTML : function() {
  9468.  9468   var h, t = this;
  9469.  9469  
  9470.  9470   h = DOM.createHTML('option', {value : ''}, '-- ' + t.settings.title + ' --');
  9471.  9471  
  9472.  9472   each(t.items, function(it) {
  9473.  9473   h += DOM.createHTML('option', {value : it.value}, it.title);
  9474.  9474   });
  9475.  9475  
  9476.  9476   h = DOM.createHTML('select', {id : t.id, 'class' : 'mceNativeListBox', 'aria-labelledby': t.id + '_aria'}, h);
  9477.  9477   h += DOM.createHTML('span', {id : t.id + '_aria', 'style': 'display: none'}, t.settings.title);
  9478.  9478   return h;
  9479.  9479   },
  9480.  9480  
  9481.  9481   postRender : function() {
  9482.  9482   var t = this, ch, changeListenerAdded = true;
  9483.  9483  
  9484.  9484   t.rendered = true;
  9485.  9485  
  9486.  9486   function onChange(e) {
  9487.  9487   var v = t.items[e.target.selectedIndex - 1];
  9488.  9488  
  9489.  9489   if (v && (v = v.value)) {
  9490.  9490   t.onChange.dispatch(t, v);
  9491.  9491  
  9492.  9492   if (t.settings.onselect)
  9493.  9493   t.settings.onselect(v);
  9494.  9494   }
  9495.  9495   };
  9496.  9496  
  9497.  9497   Event.add(t.id, 'change', onChange);
  9498.  9498  
  9499.  9499   // Accessibility keyhandler
  9500.  9500   Event.add(t.id, 'keydown', function(e) {
  9501.  9501   var bf;
  9502.  9502  
  9503.  9503   Event.remove(t.id, 'change', ch);
  9504.  9504   changeListenerAdded = false;
  9505.  9505  
  9506.  9506   bf = Event.add(t.id, 'blur', function() {
  9507.  9507   if (changeListenerAdded) return;
  9508.  9508   changeListenerAdded = true;
  9509.  9509   Event.add(t.id, 'change', onChange);
  9510.  9510   Event.remove(t.id, 'blur', bf);
  9511.  9511   });
  9512.  9512  
  9513.  9513   if (e.keyCode == 13 || e.keyCode == 32) {
  9514.  9514   onChange(e);
  9515.  9515   return Event.cancel(e);
  9516.  9516   }
  9517.  9517   });
  9518.  9518  
  9519.  9519   t.onPostRender.dispatch(t, DOM.get(t.id));
  9520.  9520   }
  9521.  9521   });
  9522.  9522  })(tinymce);
  9523.  9523  (function(tinymce) {
  9524.  9524   var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each;
  9525.  9525  
  9526.  9526   tinymce.create('tinymce.ui.MenuButton:tinymce.ui.Button', {
  9527.  9527   MenuButton : function(id, s, ed) {
  9528.  9528   this.parent(id, s, ed);
  9529.  9529  
  9530.  9530   this.onRenderMenu = new tinymce.util.Dispatcher(this);
  9531.  9531  
  9532.  9532   s.menu_container = s.menu_container || DOM.doc.body;
  9533.  9533   },
  9534.  9534  
  9535.  9535   showMenu : function() {
  9536.  9536   var t = this, p1, p2, e = DOM.get(t.id), m;
  9537.  9537  
  9538.  9538   if (t.isDisabled())
  9539.  9539   return;
  9540.  9540  
  9541.  9541   if (!t.isMenuRendered) {
  9542.  9542   t.renderMenu();
  9543.  9543   t.isMenuRendered = true;
  9544.  9544   }
  9545.  9545  
  9546.  9546   if (t.isMenuVisible)
  9547.  9547   return t.hideMenu();
  9548.  9548  
  9549.  9549   p1 = DOM.getPos(t.settings.menu_container);
  9550.  9550   p2 = DOM.getPos(e);
  9551.  9551  
  9552.  9552   m = t.menu;
  9553.  9553   m.settings.offset_x = p2.x;
  9554.  9554   m.settings.offset_y = p2.y;
  9555.  9555   m.settings.vp_offset_x = p2.x;
  9556.  9556   m.settings.vp_offset_y = p2.y;
  9557.  9557   m.settings.keyboard_focus = t._focused;
  9558.  9558   m.showMenu(0, e.clientHeight);
  9559.  9559  
  9560.  9560   Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
  9561.  9561   t.setState('Selected', 1);
  9562.  9562  
  9563.  9563   t.isMenuVisible = 1;
  9564.  9564   },
  9565.  9565  
  9566.  9566   renderMenu : function() {
  9567.  9567   var t = this, m;
  9568.  9568  
  9569.  9569   m = t.settings.control_manager.createDropMenu(t.id + '_menu', {
  9570.  9570   menu_line : 1,
  9571.  9571   'class' : this.classPrefix + 'Menu',
  9572.  9572   icons : t.settings.icons
  9573.  9573   });
  9574.  9574  
  9575.  9575   m.onHideMenu.add(function() {
  9576.  9576   t.hideMenu();
  9577.  9577   t.focus();
  9578.  9578   });
  9579.  9579  
  9580.  9580   t.onRenderMenu.dispatch(t, m);
  9581.  9581   t.menu = m;
  9582.  9582   },
  9583.  9583  
  9584.  9584   hideMenu : function(e) {
  9585.  9585   var t = this;
  9586.  9586  
  9587.  9587   // Prevent double toogles by canceling the mouse click event to the button
  9588.  9588   if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id || e.id === t.id + '_open';}))
  9589.  9589   return;
  9590.  9590  
  9591.  9591   if (!e || !DOM.getParent(e.target, '.mceMenu')) {
  9592.  9592   t.setState('Selected', 0);
  9593.  9593   Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
  9594.  9594   if (t.menu)
  9595.  9595   t.menu.hideMenu();
  9596.  9596   }
  9597.  9597  
  9598.  9598   t.isMenuVisible = 0;
  9599.  9599   },
  9600.  9600  
  9601.  9601   postRender : function() {
  9602.  9602   var t = this, s = t.settings;
  9603.  9603  
  9604.  9604   Event.add(t.id, 'click', function() {
  9605.  9605   if (!t.isDisabled()) {
  9606.  9606   if (s.onclick)
  9607.  9607   s.onclick(t.value);
  9608.  9608  
  9609.  9609   t.showMenu();
  9610.  9610   }
  9611.  9611   });
  9612.  9612   }
  9613.  9613   });
  9614.  9614  })(tinymce);
  9615.  9615  
  9616.  9616  (function(tinymce) {
  9617.  9617   var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each;
  9618.  9618  
  9619.  9619   tinymce.create('tinymce.ui.SplitButton:tinymce.ui.MenuButton', {
  9620.  9620   SplitButton : function(id, s, ed) {
  9621.  9621   this.parent(id, s, ed);
  9622.  9622   this.classPrefix = 'mceSplitButton';
  9623.  9623   },
  9624.  9624  
  9625.  9625   renderHTML : function() {
  9626.  9626   var h, t = this, s = t.settings, h1;
  9627.  9627  
  9628.  9628   h = '<tbody><tr>';
  9629.  9629  
  9630.  9630   if (s.image)
  9631.  9631   h1 = DOM.createHTML('img ', {src : s.image, role: 'presentation', 'class' : 'mceAction ' + s['class']});
  9632.  9632   else
  9633.  9633   h1 = DOM.createHTML('span', {'class' : 'mceAction ' + s['class']}, '');
  9634.  9634  
  9635.  9635   h1 += DOM.createHTML('span', {'class': 'mceVoiceLabel mceIconOnly', id: t.id + '_voice', style: 'display:none;'}, s.title);
  9636.  9636   h += '<td >' + DOM.createHTML('a', {role: 'button', id : t.id + '_action', tabindex: '-1', href : 'javascript:;', 'class' : 'mceAction ' + s['class'], onclick : "return false;", onmousedown : 'return false;', title : s.title}, h1) + '</td>';
  9637.  9637  
  9638.  9638   h1 = DOM.createHTML('span', {'class' : 'mceOpen ' + s['class']}, '<span style="display:none;" class="mceIconOnly" aria-hidden="true">\u25BC</span>');
  9639.  9639   h += '<td >' + DOM.createHTML('a', {role: 'button', id : t.id + '_open', tabindex: '-1', href : 'javascript:;', 'class' : 'mceOpen ' + s['class'], onclick : "return false;", onmousedown : 'return false;', title : s.title}, h1) + '</td>';
  9640.  9640  
  9641.  9641   h += '</tr></tbody>';
  9642.  9642   h = DOM.createHTML('table', {id : t.id, role: 'presentation', tabindex: '0', 'class' : 'mceSplitButton mceSplitButtonEnabled ' + s['class'], cellpadding : '0', cellspacing : '0', title : s.title}, h);
  9643.  9643   return DOM.createHTML('span', {role: 'button', 'aria-labelledby': t.id + '_voice', 'aria-haspopup': 'true'}, h);
  9644.  9644   },
  9645.  9645  
  9646.  9646   postRender : function() {
  9647.  9647   var t = this, s = t.settings, activate;
  9648.  9648  
  9649.  9649   if (s.onclick) {
  9650.  9650   activate = function(evt) {
  9651.  9651   if (!t.isDisabled()) {
  9652.  9652   s.onclick(t.value);
  9653.  9653   Event.cancel(evt);
  9654.  9654   }
  9655.  9655   };
  9656.  9656   Event.add(t.id + '_action', 'click', activate);
  9657.  9657   Event.add(t.id, ['click', 'keydown'], function(evt) {
  9658.  9658   var DOM_VK_SPACE = 32, DOM_VK_ENTER = 14, DOM_VK_RETURN = 13, DOM_VK_UP = 38, DOM_VK_DOWN = 40;
  9659.  9659   if ((evt.keyCode === 32 || evt.keyCode === 13 || evt.keyCode === 14) && !evt.altKey && !evt.ctrlKey && !evt.metaKey) {
  9660.  9660   activate();
  9661.  9661   Event.cancel(evt);
  9662.  9662   } else if (evt.type === 'click' || evt.keyCode === DOM_VK_DOWN) {
  9663.  9663   t.showMenu();
  9664.  9664   Event.cancel(evt);
  9665.  9665   }
  9666.  9666   });
  9667.  9667   }
  9668.  9668  
  9669.  9669   Event.add(t.id + '_open', 'click', function (evt) {
  9670.  9670   t.showMenu();
  9671.  9671   Event.cancel(evt);
  9672.  9672   });
  9673.  9673   Event.add([t.id, t.id + '_open'], 'focus', function() {t._focused = 1;});
  9674.  9674   Event.add([t.id, t.id + '_open'], 'blur', function() {t._focused = 0;});
  9675.  9675  
  9676.  9676   // Old IE doesn't have hover on all elements
  9677.  9677   if (tinymce.isIE6 || !DOM.boxModel) {
  9678.  9678   Event.add(t.id, 'mouseover', function() {
  9679.  9679   if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled'))
  9680.  9680   DOM.addClass(t.id, 'mceSplitButtonHover');
  9681.  9681   });
  9682.  9682  
  9683.  9683   Event.add(t.id, 'mouseout', function() {
  9684.  9684   if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled'))
  9685.  9685   DOM.removeClass(t.id, 'mceSplitButtonHover');
  9686.  9686   });
  9687.  9687   }
  9688.  9688   },
  9689.  9689  
  9690.  9690   destroy : function() {
  9691.  9691   this.parent();
  9692.  9692  
  9693.  9693   Event.clear(this.id + '_action');
  9694.  9694   Event.clear(this.id + '_open');
  9695.  9695   Event.clear(this.id);
  9696.  9696   }
  9697.  9697   });
  9698.  9698  })(tinymce);
  9699.  9699  
  9700.  9700  (function(tinymce) {
  9701.  9701   var DOM = tinymce.DOM, Event = tinymce.dom.Event, is = tinymce.is, each = tinymce.each;
  9702.  9702  
  9703.  9703   tinymce.create('tinymce.ui.ColorSplitButton:tinymce.ui.SplitButton', {
  9704.  9704   ColorSplitButton : function(id, s, ed) {
  9705.  9705   var t = this;
  9706.  9706  
  9707.  9707   t.parent(id, s, ed);
  9708.  9708  
  9709.  9709   t.settings = s = tinymce.extend({
  9710.  9710   colors : '000000,993300,333300,003300,003366,000080,333399,333333,800000,FF6600,808000,008000,008080,0000FF,666699,808080,FF0000,FF9900,99CC00,339966,33CCCC,3366FF,800080,999999,FF00FF,FFCC00,FFFF00,00FF00,00FFFF,00CCFF,993366,C0C0C0,FF99CC,FFCC99,FFFF99,CCFFCC,CCFFFF,99CCFF,CC99FF,FFFFFF',
  9711.  9711   grid_width : 8,
  9712.  9712   default_color : '#888888'
  9713.  9713   }, t.settings);
  9714.  9714  
  9715.  9715   t.onShowMenu = new tinymce.util.Dispatcher(t);
  9716.  9716  
  9717.  9717   t.onHideMenu = new tinymce.util.Dispatcher(t);
  9718.  9718  
  9719.  9719   t.value = s.default_color;
  9720.  9720   },
  9721.  9721  
  9722.  9722   showMenu : function() {
  9723.  9723   var t = this, r, p, e, p2;
  9724.  9724  
  9725.  9725   if (t.isDisabled())
  9726.  9726   return;
  9727.  9727  
  9728.  9728   if (!t.isMenuRendered) {
  9729.  9729   t.renderMenu();
  9730.  9730   t.isMenuRendered = true;
  9731.  9731   }
  9732.  9732  
  9733.  9733   if (t.isMenuVisible)
  9734.  9734   return t.hideMenu();
  9735.  9735  
  9736.  9736   e = DOM.get(t.id);
  9737.  9737   DOM.show(t.id + '_menu');
  9738.  9738   DOM.addClass(e, 'mceSplitButtonSelected');
  9739.  9739   p2 = DOM.getPos(e);
  9740.  9740   DOM.setStyles(t.id + '_menu', {
  9741.  9741   left : p2.x,
  9742.  9742   top : p2.y + e.clientHeight,
  9743.  9743   zIndex : 200000
  9744.  9744   });
  9745.  9745   e = 0;
  9746.  9746  
  9747.  9747   Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
  9748.  9748   t.onShowMenu.dispatch(t);
  9749.  9749  
  9750.  9750   if (t._focused) {
  9751.  9751   t._keyHandler = Event.add(t.id + '_menu', 'keydown', function(e) {
  9752.  9752   if (e.keyCode == 27)
  9753.  9753   t.hideMenu();
  9754.  9754   });
  9755.  9755  
  9756.  9756   DOM.select('a', t.id + '_menu')[0].focus(); // Select first link
  9757.  9757   }
  9758.  9758  
  9759.  9759   t.isMenuVisible = 1;
  9760.  9760   },
  9761.  9761  
  9762.  9762   hideMenu : function(e) {
  9763.  9763   var t = this;
  9764.  9764  
  9765.  9765   // Prevent double toogles by canceling the mouse click event to the button
  9766.  9766   if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id + '_open';}))
  9767.  9767   return;
  9768.  9768  
  9769.  9769   if (!e || !DOM.getParent(e.target, '.mceSplitButtonMenu')) {
  9770.  9770   DOM.removeClass(t.id, 'mceSplitButtonSelected');
  9771.  9771   Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
  9772.  9772   Event.remove(t.id + '_menu', 'keydown', t._keyHandler);
  9773.  9773   DOM.hide(t.id + '_menu');
  9774.  9774   }
  9775.  9775  
  9776.  9776   t.onHideMenu.dispatch(t);
  9777.  9777  
  9778.  9778   t.isMenuVisible = 0;
  9779.  9779   t.editor.focus();
  9780.  9780   },
  9781.  9781  
  9782.  9782   renderMenu : function() {
  9783.  9783   var t = this, m, i = 0, s = t.settings, n, tb, tr, w, context;
  9784.  9784  
  9785.  9785   w = DOM.add(s.menu_container, 'div', {role: 'listbox', id : t.id + '_menu', 'class' : s['menu_class'] + ' ' + s['class'], style : 'position:absolute;left:0;top:-1000px;'});
  9786.  9786   m = DOM.add(w, 'div', {'class' : s['class'] + ' mceSplitButtonMenu'});
  9787.  9787   DOM.add(m, 'span', {'class' : 'mceMenuLine'});
  9788.  9788  
  9789.  9789   n = DOM.add(m, 'table', {role: 'presentation', 'class' : 'mceColorSplitMenu'});
  9790.  9790   tb = DOM.add(n, 'tbody');
  9791.  9791  
  9792.  9792   // Generate color grid
  9793.  9793   i = 0;
  9794.  9794   each(is(s.colors, 'array') ? s.colors : s.colors.split(','), function(c) {
  9795.  9795   c = c.replace(/^#/, '');
  9796.  9796  
  9797.  9797   if (!i--) {
  9798.  9798   tr = DOM.add(tb, 'tr');
  9799.  9799   i = s.grid_width - 1;
  9800.  9800   }
  9801.  9801  
  9802.  9802   n = DOM.add(tr, 'td');
  9803.  9803   n = DOM.add(n, 'a', {
  9804.  9804   role : 'option',
  9805.  9805   href : 'javascript:;',
  9806.  9806   style : {
  9807.  9807   backgroundColor : '#' + c
  9808.  9808   },
  9809.  9809   'title': t.editor.getLang('colors.' + c, c),
  9810.  9810   'data-mce-color' : '#' + c
  9811.  9811   });
  9812.  9812  
  9813.  9813   if (t.editor.forcedHighContrastMode) {
  9814.  9814   n = DOM.add(n, 'canvas', { width: 16, height: 16, 'aria-hidden': 'true' });
  9815.  9815   if (n.getContext && (context = n.getContext("2d"))) {
  9816.  9816   context.fillStyle = '#' + c;
  9817.  9817   context.fillRect(0, 0, 16, 16);
  9818.  9818   } else {
  9819.  9819   // No point leaving a canvas element around if it's not supported for drawing on anyway.
  9820.  9820   DOM.remove(n);
  9821.  9821   }
  9822.  9822   }
  9823.  9823   });
  9824.  9824  
  9825.  9825   if (s.more_colors_func) {
  9826.  9826   n = DOM.add(tb, 'tr');
  9827.  9827   n = DOM.add(n, 'td', {colspan : s.grid_width, 'class' : 'mceMoreColors'});
  9828.  9828   n = DOM.add(n, 'a', {role: 'option', id : t.id + '_more', href : 'javascript:;', onclick : 'return false;', 'class' : 'mceMoreColors'}, s.more_colors_title);
  9829.  9829  
  9830.  9830   Event.add(n, 'click', function(e) {
  9831.  9831   s.more_colors_func.call(s.more_colors_scope || this);
  9832.  9832   return Event.cancel(e); // Cancel to fix onbeforeunload problem
  9833.  9833   });
  9834.  9834   }
  9835.  9835  
  9836.  9836   DOM.addClass(m, 'mceColorSplitMenu');
  9837.  9837  
  9838.  9838   new tinymce.ui.KeyboardNavigation({
  9839.  9839   root: t.id + '_menu',
  9840.  9840   items: DOM.select('a', t.id + '_menu'),
  9841.  9841   onCancel: function() {
  9842.  9842   t.hideMenu();
  9843.  9843   t.focus();
  9844.  9844   }
  9845.  9845   });
  9846.  9846  
  9847.  9847   // Prevent IE from scrolling and hindering click to occur #4019
  9848.  9848   Event.add(t.id + '_menu', 'mousedown', function(e) {return Event.cancel(e);});
  9849.  9849  
  9850.  9850   Event.add(t.id + '_menu', 'click', function(e) {
  9851.  9851   var c;
  9852.  9852  
  9853.  9853   e = DOM.getParent(e.target, 'a', tb);
  9854.  9854  
  9855.  9855   if (e && e.nodeName.toLowerCase() == 'a' && (c = e.getAttribute('data-mce-color')))
  9856.  9856   t.setColor(c);
  9857.  9857  
  9858.  9858   return Event.cancel(e); // Prevent IE auto save warning
  9859.  9859   });
  9860.  9860  
  9861.  9861   return w;
  9862.  9862   },
  9863.  9863  
  9864.  9864   setColor : function(c) {
  9865.  9865   this.displayColor(c);
  9866.  9866   this.hideMenu();
  9867.  9867   this.settings.onselect(c);
  9868.  9868   },
  9869.  9869  
  9870.  9870   displayColor : function(c) {
  9871.  9871   var t = this;
  9872.  9872  
  9873.  9873   DOM.setStyle(t.id + '_preview', 'backgroundColor', c);
  9874.  9874  
  9875.  9875   t.value = c;
  9876.  9876   },
  9877.  9877  
  9878.  9878   postRender : function() {
  9879.  9879   var t = this, id = t.id;
  9880.  9880  
  9881.  9881   t.parent();
  9882.  9882   DOM.add(id + '_action', 'div', {id : id + '_preview', 'class' : 'mceColorPreview'});
  9883.  9883   DOM.setStyle(t.id + '_preview', 'backgroundColor', t.value);
  9884.  9884   },
  9885.  9885  
  9886.  9886   destroy : function() {
  9887.  9887   this.parent();
  9888.  9888  
  9889.  9889   Event.clear(this.id + '_menu');
  9890.  9890   Event.clear(this.id + '_more');
  9891.  9891   DOM.remove(this.id + '_menu');
  9892.  9892   }
  9893.  9893   });
  9894.  9894  })(tinymce);
  9895.  9895  
  9896.  9896  (function(tinymce) {
  9897.  9897  // Shorten class names
  9898.  9898  var dom = tinymce.DOM, each = tinymce.each, Event = tinymce.dom.Event;
  9899.  9899  tinymce.create('tinymce.ui.ToolbarGroup:tinymce.ui.Container', {
  9900.  9900   renderHTML : function() {
  9901.  9901   var t = this, h = [], controls = t.controls, each = tinymce.each, settings = t.settings;
  9902.  9902  
  9903.  9903   h.push('<div id="' + t.id + '" role="group" aria-labelledby="' + t.id + '_voice">');
  9904.  9904   //TODO: ACC test this out - adding a role = application for getting the landmarks working well.
  9905.  9905   h.push("<span role='application'>");
  9906.  9906   h.push('<span id="' + t.id + '_voice" class="mceVoiceLabel" style="display:none;">' + dom.encode(settings.name) + '</span>');
  9907.  9907   each(controls, function(toolbar) {
  9908.  9908   h.push(toolbar.renderHTML());
  9909.  9909   });
  9910.  9910   h.push("</span>");
  9911.  9911   h.push('</div>');
  9912.  9912  
  9913.  9913   return h.join('');
  9914.  9914   },
  9915.  9915  
  9916.  9916   focus : function() {
  9917.  9917   this.keyNav.focus();
  9918.  9918   },
  9919.  9919  
  9920.  9920   postRender : function() {
  9921.  9921   var t = this, items = [];
  9922.  9922  
  9923.  9923   each(t.controls, function(toolbar) {
  9924.  9924   each (toolbar.controls, function(control) {
  9925.  9925   if (control.id) {
  9926.  9926   items.push(control);
  9927.  9927   }
  9928.  9928   });
  9929.  9929   });
  9930.  9930  
  9931.  9931   t.keyNav = new tinymce.ui.KeyboardNavigation({
  9932.  9932   root: t.id,
  9933.  9933   items: items,
  9934.  9934   onCancel: function() {
  9935.  9935   t.editor.focus();
  9936.  9936   },
  9937.  9937   excludeFromTabOrder: !t.settings.tab_focus_toolbar
  9938.  9938   });
  9939.  9939   },
  9940.  9940  
  9941.  9941   destroy : function() {
  9942.  9942   var self = this;
  9943.  9943  
  9944.  9944   self.parent();
  9945.  9945   self.keyNav.destroy();
  9946.  9946   Event.clear(self.id);
  9947.  9947   }
  9948.  9948  });
  9949.  9949  })(tinymce);
  9950.  9950  
  9951.  9951  (function(tinymce) {
  9952.  9952  // Shorten class names
  9953.  9953  var dom = tinymce.DOM, each = tinymce.each
  9954.  9954  tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
  9955.  9955   renderHTML : function() {
  9956.  9956   var t = this, h = '', c, co, s = t.settings, i, pr, nx, cl;
  9957.  9957  
  9958.  9958   cl = t.controls;
  9959.  9959   for (i=0; i<cl.length; i++) {
  9960.  9960   // Get current control, prev control, next control and if the control is a list box or not
  9961.  9961   co = cl[i];
  9962.  9962   pr = cl[i - 1];
  9963.  9963   nx = cl[i + 1];
  9964.  9964  
  9965.  9965   // Add toolbar start
  9966.  9966   if (i === 0) {
  9967.  9967   c = 'mceToolbarStart';
  9968.  9968  
  9969.  9969   if (co.Button)
  9970.  9970   c += ' mceToolbarStartButton';
  9971.  9971   else if (co.SplitButton)
  9972.  9972   c += ' mceToolbarStartSplitButton';
  9973.  9973   else if (co.ListBox)
  9974.  9974   c += ' mceToolbarStartListBox';
  9975.  9975  
  9976.  9976   h += dom.createHTML('td', {'class' : c}, dom.createHTML('span', null, '<!-- IE -->'));
  9977.  9977   }
  9978.  9978  
  9979.  9979   // Add toolbar end before list box and after the previous button
  9980.  9980   // This is to fix the o2k7 editor skins
  9981.  9981   if (pr && co.ListBox) {
  9982.  9982   if (pr.Button || pr.SplitButton)
  9983.  9983   h += dom.createHTML('td', {'class' : 'mceToolbarEnd'}, dom.createHTML('span', null, '<!-- IE -->'));
  9984.  9984   }
  9985.  9985  
  9986.  9986   // Render control HTML
  9987.  9987  
  9988.  9988   // IE 8 quick fix, needed to propertly generate a hit area for anchors
  9989.  9989   if (dom.stdMode)
  9990.  9990   h += '<td style="position: relative">' + co.renderHTML() + '</td>';
  9991.  9991   else
  9992.  9992   h += '<td>' + co.renderHTML() + '</td>';
  9993.  9993  
  9994.  9994   // Add toolbar start after list box and before the next button
  9995.  9995   // This is to fix the o2k7 editor skins
  9996.  9996   if (nx && co.ListBox) {
  9997.  9997   if (nx.Button || nx.SplitButton)
  9998.  9998   h += dom.createHTML('td', {'class' : 'mceToolbarStart'}, dom.createHTML('span', null, '<!-- IE -->'));
  9999.  9999   }
  10000. 10000   }
  10001. 10001  
  10002. 10002   c = 'mceToolbarEnd';
  10003. 10003  
  10004. 10004   if (co.Button)
  10005. 10005   c += ' mceToolbarEndButton';
  10006. 10006   else if (co.SplitButton)
  10007. 10007   c += ' mceToolbarEndSplitButton';
  10008. 10008   else if (co.ListBox)
  10009. 10009   c += ' mceToolbarEndListBox';
  10010. 10010  
  10011. 10011   h += dom.createHTML('td', {'class' : c}, dom.createHTML('span', null, '<!-- IE -->'));
  10012. 10012  
  10013. 10013   return dom.createHTML('table', {id : t.id, 'class' : 'mceToolbar' + (s['class'] ? ' ' + s['class'] : ''), cellpadding : '0', cellspacing : '0', align : t.settings.align || '', role: 'presentation', tabindex: '-1'}, '<tbody><tr>' + h + '</tr></tbody>');
  10014. 10014   }
  10015. 10015  });
  10016. 10016  })(tinymce);
  10017. 10017  
  10018. 10018  (function(tinymce) {
  10019. 10019   var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each;
  10020. 10020  
  10021. 10021   tinymce.create('tinymce.AddOnManager', {
  10022. 10022   AddOnManager : function() {
  10023. 10023   var self = this;
  10024. 10024  
  10025. 10025   self.items = [];
  10026. 10026   self.urls = {};
  10027. 10027   self.lookup = {};
  10028. 10028   self.onAdd = new Dispatcher(self);
  10029. 10029   },
  10030. 10030  
  10031. 10031   get : function(n) {
  10032. 10032   return this.lookup[n];
  10033. 10033   },
  10034. 10034  
  10035. 10035   requireLangPack : function(n) {
  10036. 10036   var s = tinymce.settings;
  10037. 10037  
  10038. 10038   if (s && s.language && s.language_load !== false)
  10039. 10039   tinymce.ScriptLoader.add(this.urls[n] + '/langs/' + s.language + '.js');
  10040. 10040   },
  10041. 10041  
  10042. 10042   add : function(id, o) {
  10043. 10043   this.items.push(o);
  10044. 10044   this.lookup[id] = o;
  10045. 10045   this.onAdd.dispatch(this, id, o);
  10046. 10046  
  10047. 10047   return o;
  10048. 10048   },
  10049. 10049  
  10050. 10050   load : function(n, u, cb, s) {
  10051. 10051   var t = this;
  10052. 10052  
  10053. 10053   if (t.urls[n])
  10054. 10054   return;
  10055. 10055  
  10056. 10056   if (u.indexOf('/') != 0 && u.indexOf('://') == -1)
  10057. 10057   u = tinymce.baseURL + '/' + u;
  10058. 10058  
  10059. 10059   t.urls[n] = u.substring(0, u.lastIndexOf('/'));
  10060. 10060  
  10061. 10061   if (!t.lookup[n])
  10062. 10062   tinymce.ScriptLoader.add(u, cb, s);
  10063. 10063   }
  10064. 10064   });
  10065. 10065  
  10066. 10066   // Create plugin and theme managers
  10067. 10067   tinymce.PluginManager = new tinymce.AddOnManager();
  10068. 10068   tinymce.ThemeManager = new tinymce.AddOnManager();
  10069. 10069  }(tinymce));
  10070. 10070  
  10071. 10071  (function(tinymce) {
  10072. 10072   // Shorten names
  10073. 10073   var each = tinymce.each, extend = tinymce.extend,
  10074. 10074   DOM = tinymce.DOM, Event = tinymce.dom.Event,
  10075. 10075   ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager,
  10076. 10076   explode = tinymce.explode,
  10077. 10077   Dispatcher = tinymce.util.Dispatcher, undefined, instanceCounter = 0;
  10078. 10078  
  10079. 10079   // Setup some URLs where the editor API is located and where the document is
  10080. 10080   tinymce.documentBaseURL = window.location.href.replace(/[\?#].*$/, '').replace(/[\/\\][^\/]+$/, '');
  10081. 10081   if (!/[\/\\]$/.test(tinymce.documentBaseURL))
  10082. 10082   tinymce.documentBaseURL += '/';
  10083. 10083  
  10084. 10084   tinymce.baseURL = new tinymce.util.URI(tinymce.documentBaseURL).toAbsolute(tinymce.baseURL);
  10085. 10085  
  10086. 10086   tinymce.baseURI = new tinymce.util.URI(tinymce.baseURL);
  10087. 10087  
  10088. 10088   // Add before unload listener
  10089. 10089   // This was required since IE was leaking memory if you added and removed beforeunload listeners
  10090. 10090   // with attachEvent/detatchEvent so this only adds one listener and instances can the attach to the onBeforeUnload event
  10091. 10091   tinymce.onBeforeUnload = new Dispatcher(tinymce);
  10092. 10092  
  10093. 10093   // Must be on window or IE will leak if the editor is placed in frame or iframe
  10094. 10094   Event.add(window, 'beforeunload', function(e) {
  10095. 10095   tinymce.onBeforeUnload.dispatch(tinymce, e);
  10096. 10096   });
  10097. 10097  
  10098. 10098   tinymce.onAddEditor = new Dispatcher(tinymce);
  10099. 10099  
  10100. 10100   tinymce.onRemoveEditor = new Dispatcher(tinymce);
  10101. 10101  
  10102. 10102   tinymce.EditorManager = extend(tinymce, {
  10103. 10103   editors : [],
  10104. 10104  
  10105. 10105   i18n : {},
  10106. 10106  
  10107. 10107   activeEditor : null,
  10108. 10108  
  10109. 10109   init : function(s) {
  10110. 10110   var t = this, pl, sl = tinymce.ScriptLoader, e, el = [], ed;
  10111. 10111  
  10112. 10112   function execCallback(se, n, s) {
  10113. 10113   var f = se[n];
  10114. 10114  
  10115. 10115   if (!f)
  10116. 10116   return;
  10117. 10117  
  10118. 10118   if (tinymce.is(f, 'string')) {
  10119. 10119   s = f.replace(/\.\w+$/, '');
  10120. 10120   s = s ? tinymce.resolve(s) : 0;
  10121. 10121   f = tinymce.resolve(f);
  10122. 10122   }
  10123. 10123  
  10124. 10124   return f.apply(s || this, Array.prototype.slice.call(arguments, 2));
  10125. 10125   };
  10126. 10126  
  10127. 10127   s = extend({
  10128. 10128   theme : "simple",
  10129. 10129   language : "en"
  10130. 10130   }, s);
  10131. 10131  
  10132. 10132   t.settings = s;
  10133. 10133  
  10134. 10134   // Legacy call
  10135. 10135   Event.add(document, 'init', function() {
  10136. 10136   var l, co;
  10137. 10137  
  10138. 10138   execCallback(s, 'onpageload');
  10139. 10139  
  10140. 10140   switch (s.mode) {
  10141. 10141   case "exact":
  10142. 10142   l = s.elements || '';
  10143. 10143  
  10144. 10144   if(l.length > 0) {
  10145. 10145   each(explode(l), function(v) {
  10146. 10146   if (DOM.get(v)) {
  10147. 10147   ed = new tinymce.Editor(v, s);
  10148. 10148   el.push(ed);
  10149. 10149   ed.render(1);
  10150. 10150   } else {
  10151. 10151   each(document.forms, function(f) {
  10152. 10152   each(f.elements, function(e) {
  10153. 10153   if (e.name === v) {
  10154. 10154   v = 'mce_editor_' + instanceCounter++;
  10155. 10155   DOM.setAttrib(e, 'id', v);
  10156. 10156  
  10157. 10157   ed = new tinymce.Editor(v, s);
  10158. 10158   el.push(ed);
  10159. 10159   ed.render(1);
  10160. 10160   }
  10161. 10161   });
  10162. 10162   });
  10163. 10163   }
  10164. 10164   });
  10165. 10165   }
  10166. 10166   break;
  10167. 10167  
  10168. 10168   case "textareas":
  10169. 10169   case "specific_textareas":
  10170. 10170   function hasClass(n, c) {
  10171. 10171   return c.constructor === RegExp ? c.test(n.className) : DOM.hasClass(n, c);
  10172. 10172   };
  10173. 10173  
  10174. 10174   each(DOM.select('textarea'), function(v) {
  10175. 10175   if (s.editor_deselector && hasClass(v, s.editor_deselector))
  10176. 10176   return;
  10177. 10177  
  10178. 10178   if (!s.editor_selector || hasClass(v, s.editor_selector)) {
  10179. 10179   // Can we use the name
  10180. 10180   e = DOM.get(v.name);
  10181. 10181   if (!v.id && !e)
  10182. 10182   v.id = v.name;
  10183. 10183  
  10184. 10184   // Generate unique name if missing or already exists
  10185. 10185   if (!v.id || t.get(v.id))
  10186. 10186   v.id = DOM.uniqueId();
  10187. 10187  
  10188. 10188   ed = new tinymce.Editor(v.id, s);
  10189. 10189   el.push(ed);
  10190. 10190   ed.render(1);
  10191. 10191   }
  10192. 10192   });
  10193. 10193   break;
  10194. 10194   }
  10195. 10195  
  10196. 10196   // Call onInit when all editors are initialized
  10197. 10197   if (s.oninit) {
  10198. 10198   l = co = 0;
  10199. 10199  
  10200. 10200   each(el, function(ed) {
  10201. 10201   co++;
  10202. 10202  
  10203. 10203   if (!ed.initialized) {
  10204. 10204   // Wait for it
  10205. 10205   ed.onInit.add(function() {
  10206. 10206   l++;
  10207. 10207  
  10208. 10208   // All done
  10209. 10209   if (l == co)
  10210. 10210   execCallback(s, 'oninit');
  10211. 10211   });
  10212. 10212   } else
  10213. 10213   l++;
  10214. 10214  
  10215. 10215   // All done
  10216. 10216   if (l == co)
  10217. 10217   execCallback(s, 'oninit');
  10218. 10218   });
  10219. 10219   }
  10220. 10220   });
  10221. 10221   },
  10222. 10222  
  10223. 10223   get : function(id) {
  10224. 10224   if (id === undefined)
  10225. 10225   return this.editors;
  10226. 10226  
  10227. 10227   return this.editors[id];
  10228. 10228   },
  10229. 10229  
  10230. 10230   getInstanceById : function(id) {
  10231. 10231   return this.get(id);
  10232. 10232   },
  10233. 10233  
  10234. 10234   add : function(editor) {
  10235. 10235   var self = this, editors = self.editors;
  10236. 10236  
  10237. 10237   // Add named and index editor instance
  10238. 10238   editors[editor.id] = editor;
  10239. 10239   editors.push(editor);
  10240. 10240  
  10241. 10241   self._setActive(editor);
  10242. 10242   self.onAddEditor.dispatch(self, editor);
  10243. 10243  
  10244. 10244  
  10245. 10245   return editor;
  10246. 10246   },
  10247. 10247  
  10248. 10248   remove : function(editor) {
  10249. 10249   var t = this, i, editors = t.editors;
  10250. 10250  
  10251. 10251   // Not in the collection
  10252. 10252   if (!editors[editor.id])
  10253. 10253   return null;
  10254. 10254  
  10255. 10255   delete editors[editor.id];
  10256. 10256  
  10257. 10257   for (i = 0; i < editors.length; i++) {
  10258. 10258   if (editors[i] == editor) {
  10259. 10259   editors.splice(i, 1);
  10260. 10260   break;
  10261. 10261   }
  10262. 10262   }
  10263. 10263  
  10264. 10264   // Select another editor since the active one was removed
  10265. 10265   if (t.activeEditor == editor)
  10266. 10266   t._setActive(editors[0]);
  10267. 10267  
  10268. 10268   editor.destroy();
  10269. 10269   t.onRemoveEditor.dispatch(t, editor);
  10270. 10270  
  10271. 10271   return editor;
  10272. 10272   },
  10273. 10273  
  10274. 10274   execCommand : function(c, u, v) {
  10275. 10275   var t = this, ed = t.get(v), w;
  10276. 10276  
  10277. 10277   // Manager commands
  10278. 10278   switch (c) {
  10279. 10279   case "mceFocus":
  10280. 10280   ed.focus();
  10281. 10281   return true;
  10282. 10282  
  10283. 10283   case "mceAddEditor":
  10284. 10284   case "mceAddControl":
  10285. 10285   if (!t.get(v))
  10286. 10286   new tinymce.Editor(v, t.settings).render();
  10287. 10287  
  10288. 10288   return true;
  10289. 10289  
  10290. 10290   case "mceAddFrameControl":
  10291. 10291   w = v.window;
  10292. 10292  
  10293. 10293   // Add tinyMCE global instance and tinymce namespace to specified window
  10294. 10294   w.tinyMCE = tinyMCE;
  10295. 10295   w.tinymce = tinymce;
  10296. 10296  
  10297. 10297   tinymce.DOM.doc = w.document;
  10298. 10298   tinymce.DOM.win = w;
  10299. 10299  
  10300. 10300   ed = new tinymce.Editor(v.element_id, v);
  10301. 10301   ed.render();
  10302. 10302  
  10303. 10303   // Fix IE memory leaks
  10304. 10304   if (tinymce.isIE) {
  10305. 10305   function clr() {
  10306. 10306   ed.destroy();
  10307. 10307   w.detachEvent('onunload', clr);
  10308. 10308   w = w.tinyMCE = w.tinymce = null; // IE leak
  10309. 10309   };
  10310. 10310  
  10311. 10311   w.attachEvent('onunload', clr);
  10312. 10312   }
  10313. 10313  
  10314. 10314   v.page_window = null;
  10315. 10315  
  10316. 10316   return true;
  10317. 10317  
  10318. 10318   case "mceRemoveEditor":
  10319. 10319   case "mceRemoveControl":
  10320. 10320   if (ed)
  10321. 10321   ed.remove();
  10322. 10322  
  10323. 10323   return true;
  10324. 10324  
  10325. 10325   case 'mceToggleEditor':
  10326. 10326   if (!ed) {
  10327. 10327   t.execCommand('mceAddControl', 0, v);
  10328. 10328   return true;
  10329. 10329   }
  10330. 10330  
  10331. 10331   if (ed.isHidden())
  10332. 10332   ed.show();
  10333. 10333   else
  10334. 10334   ed.hide();
  10335. 10335  
  10336. 10336   return true;
  10337. 10337   }
  10338. 10338  
  10339. 10339   // Run command on active editor
  10340. 10340   if (t.activeEditor)
  10341. 10341   return t.activeEditor.execCommand(c, u, v);
  10342. 10342  
  10343. 10343   return false;
  10344. 10344   },
  10345. 10345  
  10346. 10346   execInstanceCommand : function(id, c, u, v) {
  10347. 10347   var ed = this.get(id);
  10348. 10348  
  10349. 10349   if (ed)
  10350. 10350   return ed.execCommand(c, u, v);
  10351. 10351  
  10352. 10352   return false;
  10353. 10353   },
  10354. 10354  
  10355. 10355   triggerSave : function() {
  10356. 10356   each(this.editors, function(e) {
  10357. 10357   e.save();
  10358. 10358   });
  10359. 10359   },
  10360. 10360  
  10361. 10361   addI18n : function(p, o) {
  10362. 10362   var lo, i18n = this.i18n;
  10363. 10363  
  10364. 10364   if (!tinymce.is(p, 'string')) {
  10365. 10365   each(p, function(o, lc) {
  10366. 10366   each(o, function(o, g) {
  10367. 10367   each(o, function(o, k) {
  10368. 10368   if (g === 'common')
  10369. 10369   i18n[lc + '.' + k] = o;
  10370. 10370   else
  10371. 10371   i18n[lc + '.' + g + '.' + k] = o;
  10372. 10372   });
  10373. 10373   });
  10374. 10374   });
  10375. 10375   } else {
  10376. 10376   each(o, function(o, k) {
  10377. 10377   i18n[p + '.' + k] = o;
  10378. 10378   });
  10379. 10379   }
  10380. 10380   },
  10381. 10381  
  10382. 10382   // Private methods
  10383. 10383  
  10384. 10384   _setActive : function(editor) {
  10385. 10385   this.selectedInstance = this.activeEditor = editor;
  10386. 10386   }
  10387. 10387   });
  10388. 10388  })(tinymce);
  10389. 10389  
  10390. 10390  (function(tinymce) {
  10391. 10391   // Shorten these names
  10392. 10392   var DOM = tinymce.DOM, Event = tinymce.dom.Event, extend = tinymce.extend,
  10393. 10393   Dispatcher = tinymce.util.Dispatcher, each = tinymce.each, isGecko = tinymce.isGecko,
  10394. 10394   isIE = tinymce.isIE, isWebKit = tinymce.isWebKit, is = tinymce.is,
  10395. 10395   ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager,
  10396. 10396   inArray = tinymce.inArray, grep = tinymce.grep, explode = tinymce.explode;
  10397. 10397  
  10398. 10398   tinymce.create('tinymce.Editor', {
  10399. 10399   Editor : function(id, s) {
  10400. 10400   var t = this;
  10401. 10401  
  10402. 10402   t.id = t.editorId = id;
  10403. 10403  
  10404. 10404   t.execCommands = {};
  10405. 10405   t.queryStateCommands = {};
  10406. 10406   t.queryValueCommands = {};
  10407. 10407  
  10408. 10408   t.isNotDirty = false;
  10409. 10409  
  10410. 10410   t.plugins = {};
  10411. 10411  
  10412. 10412   // Add events to the editor
  10413. 10413   each([
  10414. 10414   'onPreInit',
  10415. 10415  
  10416. 10416   'onBeforeRenderUI',
  10417. 10417  
  10418. 10418   'onPostRender',
  10419. 10419  
  10420. 10420   'onInit',
  10421. 10421  
  10422. 10422   'onRemove',
  10423. 10423  
  10424. 10424   'onActivate',
  10425. 10425  
  10426. 10426   'onDeactivate',
  10427. 10427  
  10428. 10428   'onClick',
  10429. 10429  
  10430. 10430   'onEvent',
  10431. 10431  
  10432. 10432   'onMouseUp',
  10433. 10433  
  10434. 10434   'onMouseDown',
  10435. 10435  
  10436. 10436   'onDblClick',
  10437. 10437  
  10438. 10438   'onKeyDown',
  10439. 10439  
  10440. 10440   'onKeyUp',
  10441. 10441  
  10442. 10442   'onKeyPress',
  10443. 10443  
  10444. 10444   'onContextMenu',
  10445. 10445  
  10446. 10446   'onSubmit',
  10447. 10447  
  10448. 10448   'onReset',
  10449. 10449  
  10450. 10450   'onPaste',
  10451. 10451  
  10452. 10452   'onPreProcess',
  10453. 10453  
  10454. 10454   'onPostProcess',
  10455. 10455  
  10456. 10456   'onBeforeSetContent',
  10457. 10457  
  10458. 10458   'onBeforeGetContent',
  10459. 10459  
  10460. 10460   'onSetContent',
  10461. 10461  
  10462. 10462   'onGetContent',
  10463. 10463  
  10464. 10464   'onLoadContent',
  10465. 10465  
  10466. 10466   'onSaveContent',
  10467. 10467  
  10468. 10468   'onNodeChange',
  10469. 10469  
  10470. 10470   'onChange',
  10471. 10471  
  10472. 10472   'onBeforeExecCommand',
  10473. 10473  
  10474. 10474   'onExecCommand',
  10475. 10475  
  10476. 10476   'onUndo',
  10477. 10477  
  10478. 10478   'onRedo',
  10479. 10479  
  10480. 10480   'onVisualAid',
  10481. 10481  
  10482. 10482   'onSetProgressState'
  10483. 10483   ], function(e) {
  10484. 10484   t[e] = new Dispatcher(t);
  10485. 10485   });
  10486. 10486  
  10487. 10487   t.settings = s = extend({
  10488. 10488   id : id,
  10489. 10489   language : 'en',
  10490. 10490   docs_language : 'en',
  10491. 10491   theme : 'simple',
  10492. 10492   skin : 'default',
  10493. 10493   delta_width : 0,
  10494. 10494   delta_height : 0,
  10495. 10495   popup_css : '',
  10496. 10496   plugins : '',
  10497. 10497   document_base_url : tinymce.documentBaseURL,
  10498. 10498   add_form_submit_trigger : 1,
  10499. 10499   submit_patch : 1,
  10500. 10500   add_unload_trigger : 1,
  10501. 10501   convert_urls : 1,
  10502. 10502   relative_urls : 1,
  10503. 10503   remove_script_host : 1,
  10504. 10504   table_inline_editing : 0,
  10505. 10505   object_resizing : 1,
  10506. 10506   cleanup : 1,
  10507. 10507   accessibility_focus : 1,
  10508. 10508   custom_shortcuts : 1,
  10509. 10509   custom_undo_redo_keyboard_shortcuts : 1,
  10510. 10510   custom_undo_redo_restore_selection : 1,
  10511. 10511   custom_undo_redo : 1,
  10512. 10512   doctype : tinymce.isIE6 ? '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">' : '<!DOCTYPE>', // Use old doctype on IE 6 to avoid horizontal scroll
  10513. 10513   visual_table_class : 'mceItemTable',
  10514. 10514   visual : 1,
  10515. 10515   font_size_style_values : 'xx-small,x-small,small,medium,large,x-large,xx-large',
  10516. 10516   apply_source_formatting : 1,
  10517. 10517   directionality : 'ltr',
  10518. 10518   forced_root_block : 'p',
  10519. 10519   hidden_input : 1,
  10520. 10520   padd_empty_editor : 1,
  10521. 10521   render_ui : 1,
  10522. 10522   init_theme : 1,
  10523. 10523   force_p_newlines : 1,
  10524. 10524   indentation : '30px',
  10525. 10525   keep_styles : 1,
  10526. 10526   fix_table_elements : 1,
  10527. 10527   inline_styles : 1,
  10528. 10528   convert_fonts_to_spans : true,
  10529. 10529   indent : 'simple',
  10530. 10530   indent_before : 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,tfoot,tbody,tr',
  10531. 10531   indent_after : 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,tfoot,tbody,tr',
  10532. 10532   validate : true,
  10533. 10533   entity_encoding : 'named',
  10534. 10534   url_converter : t.convertURL,
  10535. 10535   url_converter_scope : t,
  10536. 10536   ie7_compat : true
  10537. 10537   }, s);
  10538. 10538  
  10539. 10539   t.documentBaseURI = new tinymce.util.URI(s.document_base_url || tinymce.documentBaseURL, {
  10540. 10540   base_uri : tinyMCE.baseURI
  10541. 10541   });
  10542. 10542  
  10543. 10543   t.baseURI = tinymce.baseURI;
  10544. 10544  
  10545. 10545   t.contentCSS = [];
  10546. 10546  
  10547. 10547   // Call setup
  10548. 10548   t.execCallback('setup', t);
  10549. 10549   },
  10550. 10550  
  10551. 10551   render : function(nst) {
  10552. 10552   var t = this, s = t.settings, id = t.id, sl = tinymce.ScriptLoader;
  10553. 10553  
  10554. 10554   // Page is not loaded yet, wait for it
  10555. 10555   if (!Event.domLoaded) {
  10556. 10556   Event.add(document, 'init', function() {
  10557. 10557   t.render();
  10558. 10558   });
  10559. 10559   return;
  10560. 10560   }
  10561. 10561  
  10562. 10562   tinyMCE.settings = s;
  10563. 10563  
  10564. 10564   // Element not found, then skip initialization
  10565. 10565   if (!t.getElement())
  10566. 10566   return;
  10567. 10567  
  10568. 10568   // Is a iPad/iPhone, then skip initialization. We need to sniff here since the
  10569. 10569   // browser says it has contentEditable support but there is no visible caret
  10570. 10570   // We will remove this check ones Apple implements full contentEditable support
  10571. 10571   if (tinymce.isIDevice)
  10572. 10572   return;
  10573. 10573  
  10574. 10574   // Add hidden input for non input elements inside form elements
  10575. 10575   if (!/TEXTAREA|INPUT/i.test(t.getElement().nodeName) && s.hidden_input && DOM.getParent(id, 'form'))
  10576. 10576   DOM.insertAfter(DOM.create('input', {type : 'hidden', name : id}), id);
  10577. 10577  
  10578. 10578   if (tinymce.WindowManager)
  10579. 10579   t.windowManager = new tinymce.WindowManager(t);
  10580. 10580  
  10581. 10581   if (s.encoding == 'xml') {
  10582. 10582   t.onGetContent.add(function(ed, o) {
  10583. 10583   if (o.save)
  10584. 10584   o.content = DOM.encode(o.content);
  10585. 10585   });
  10586. 10586   }
  10587. 10587  
  10588. 10588   if (s.add_form_submit_trigger) {
  10589. 10589   t.onSubmit.addToTop(function() {
  10590. 10590   if (t.initialized) {
  10591. 10591   t.save();
  10592. 10592   t.isNotDirty = 1;
  10593. 10593   }
  10594. 10594   });
  10595. 10595   }
  10596. 10596  
  10597. 10597   if (s.add_unload_trigger) {
  10598. 10598   t._beforeUnload = tinyMCE.onBeforeUnload.add(function() {
  10599. 10599   if (t.initialized && !t.destroyed && !t.isHidden())
  10600. 10600   t.save({format : 'raw', no_events : true});
  10601. 10601   });
  10602. 10602   }
  10603. 10603  
  10604. 10604   tinymce.addUnload(t.destroy, t);
  10605. 10605  
  10606. 10606   if (s.submit_patch) {
  10607. 10607   t.onBeforeRenderUI.add(function() {
  10608. 10608   var n = t.getElement().form;
  10609. 10609  
  10610. 10610   if (!n)
  10611. 10611   return;
  10612. 10612  
  10613. 10613   // Already patched
  10614. 10614   if (n._mceOldSubmit)
  10615. 10615   return;
  10616. 10616  
  10617. 10617   // Check page uses id="submit" or name="submit" for it's submit button
  10618. 10618   if (!n.submit.nodeType && !n.submit.length) {
  10619. 10619   t.formElement = n;
  10620. 10620   n._mceOldSubmit = n.submit;
  10621. 10621   n.submit = function() {
  10622. 10622   // Save all instances
  10623. 10623   tinymce.triggerSave();
  10624. 10624   t.isNotDirty = 1;
  10625. 10625  
  10626. 10626   return t.formElement._mceOldSubmit(t.formElement);
  10627. 10627   };
  10628. 10628   }
  10629. 10629  
  10630. 10630   n = null;
  10631. 10631   });
  10632. 10632   }
  10633. 10633  
  10634. 10634   // Load scripts
  10635. 10635   function loadScripts() {
  10636. 10636   if (s.language && s.language_load !== false)
  10637. 10637   sl.add(tinymce.baseURL + '/langs/' + s.language + '.js');
  10638. 10638  
  10639. 10639   if (s.theme && s.theme.charAt(0) != '-' && !ThemeManager.urls[s.theme])
  10640. 10640   ThemeManager.load(s.theme, 'themes/' + s.theme + '/editor_template' + tinymce.suffix + '.js');
  10641. 10641  
  10642. 10642   each(explode(s.plugins), function(p) {
  10643. 10643   if (p && p.charAt(0) != '-' && !PluginManager.urls[p]) {
  10644. 10644   // Skip safari plugin, since it is removed as of 3.3b1
  10645. 10645   if (p == 'safari')
  10646. 10646   return;
  10647. 10647  
  10648. 10648   PluginManager.load(p, 'plugins/' + p + '/editor_plugin' + tinymce.suffix + '.js');
  10649. 10649   }
  10650. 10650   });
  10651. 10651  
  10652. 10652   // Init when que is loaded
  10653. 10653   sl.loadQueue(function() {
  10654. 10654   if (!t.removed)
  10655. 10655   t.init();
  10656. 10656   });
  10657. 10657   };
  10658. 10658  
  10659. 10659   loadScripts();
  10660. 10660   },
  10661. 10661  
  10662. 10662   init : function() {
  10663. 10663   var n, t = this, s = t.settings, w, h, e = t.getElement(), o, ti, u, bi, bc, re, i;
  10664. 10664  
  10665. 10665   tinymce.add(t);
  10666. 10666  
  10667. 10667   s.aria_label = s.aria_label || DOM.getAttrib(e, 'aria-label', t.getLang('aria.rich_text_area'));
  10668. 10668  
  10669. 10669   if (s.theme) {
  10670. 10670   s.theme = s.theme.replace(/-/, '');
  10671. 10671   o = ThemeManager.get(s.theme);
  10672. 10672   t.theme = new o();
  10673. 10673  
  10674. 10674   if (t.theme.init && s.init_theme)
  10675. 10675   t.theme.init(t, ThemeManager.urls[s.theme] || tinymce.documentBaseURL.replace(/\/$/, ''));
  10676. 10676   }
  10677. 10677  
  10678. 10678   // Create all plugins
  10679. 10679   each(explode(s.plugins.replace(/\-/g, '')), function(p) {
  10680. 10680   var c = PluginManager.get(p), u = PluginManager.urls[p] || tinymce.documentBaseURL.replace(/\/$/, ''), po;
  10681. 10681  
  10682. 10682   if (c) {
  10683. 10683   po = new c(t, u);
  10684. 10684  
  10685. 10685   t.plugins[p] = po;
  10686. 10686  
  10687. 10687   if (po.init)
  10688. 10688   po.init(t, u);
  10689. 10689   }
  10690. 10690   });
  10691. 10691  
  10692. 10692   // Setup popup CSS path(s)
  10693. 10693   if (s.popup_css !== false) {
  10694. 10694   if (s.popup_css)
  10695. 10695   s.popup_css = t.documentBaseURI.toAbsolute(s.popup_css);
  10696. 10696   else
  10697. 10697   s.popup_css = t.baseURI.toAbsolute("themes/" + s.theme + "/skins/" + s.skin + "/dialog.css");
  10698. 10698   }
  10699. 10699  
  10700. 10700   if (s.popup_css_add)
  10701. 10701   s.popup_css += ',' + t.documentBaseURI.toAbsolute(s.popup_css_add);
  10702. 10702  
  10703. 10703   t.controlManager = new tinymce.ControlManager(t);
  10704. 10704  
  10705. 10705   if (s.custom_undo_redo) {
  10706. 10706   t.onBeforeExecCommand.add(function(ed, cmd, ui, val, a) {
  10707. 10707   if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!a || !a.skip_undo))
  10708. 10708   t.undoManager.beforeChange();
  10709. 10709   });
  10710. 10710  
  10711. 10711   t.onExecCommand.add(function(ed, cmd, ui, val, a) {
  10712. 10712   if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!a || !a.skip_undo))
  10713. 10713   t.undoManager.add();
  10714. 10714   });
  10715. 10715   }
  10716. 10716  
  10717. 10717   t.onExecCommand.add(function(ed, c) {
  10718. 10718   // Don't refresh the select lists until caret move
  10719. 10719   if (!/^(FontName|FontSize)$/.test(c))
  10720. 10720   t.nodeChanged();
  10721. 10721   });
  10722. 10722  
  10723. 10723   // Remove ghost selections on images and tables in Gecko
  10724. 10724   if (isGecko) {
  10725. 10725   function repaint(a, o) {
  10726. 10726   if (!o || !o.initial)
  10727. 10727   t.execCommand('mceRepaint');
  10728. 10728   };
  10729. 10729  
  10730. 10730   t.onUndo.add(repaint);
  10731. 10731   t.onRedo.add(repaint);
  10732. 10732   t.onSetContent.add(repaint);
  10733. 10733   }
  10734. 10734  
  10735. 10735   // Enables users to override the control factory
  10736. 10736   t.onBeforeRenderUI.dispatch(t, t.controlManager);
  10737. 10737  
  10738. 10738   // Measure box
  10739. 10739   if (s.render_ui) {
  10740. 10740   w = s.width || e.style.width || e.offsetWidth;
  10741. 10741   h = s.height || e.style.height || e.offsetHeight;
  10742. 10742   t.orgDisplay = e.style.display;
  10743. 10743   re = /^[0-9\.]+(|px)$/i;
  10744. 10744  
  10745. 10745   if (re.test('' + w))
  10746. 10746   w = Math.max(parseInt(w) + (o.deltaWidth || 0), 100);
  10747. 10747  
  10748. 10748   if (re.test('' + h))
  10749. 10749   h = Math.max(parseInt(h) + (o.deltaHeight || 0), 100);
  10750. 10750  
  10751. 10751   // Render UI
  10752. 10752   o = t.theme.renderUI({
  10753. 10753   targetNode : e,
  10754. 10754   width : w,
  10755. 10755   height : h,
  10756. 10756   deltaWidth : s.delta_width,
  10757. 10757   deltaHeight : s.delta_height
  10758. 10758   });
  10759. 10759  
  10760. 10760   t.editorContainer = o.editorContainer;
  10761. 10761   }
  10762. 10762  
  10763. 10763  
  10764. 10764   // User specified a document.domain value
  10765. 10765   if (document.domain && location.hostname != document.domain)
  10766. 10766   tinymce.relaxedDomain = document.domain;
  10767. 10767  
  10768. 10768   // Resize editor
  10769. 10769   DOM.setStyles(o.sizeContainer || o.editorContainer, {
  10770. 10770   width : w,
  10771. 10771   height : h
  10772. 10772   });
  10773. 10773  
  10774. 10774   // Load specified content CSS last
  10775. 10775   if (s.content_css) {
  10776. 10776   tinymce.each(explode(s.content_css), function(u) {
  10777. 10777   t.contentCSS.push(t.documentBaseURI.toAbsolute(u));
  10778. 10778   });
  10779. 10779   }
  10780. 10780  
  10781. 10781   h = (o.iframeHeight || h) + (typeof(h) == 'number' ? (o.deltaHeight || 0) : '');
  10782. 10782   if (h < 100)
  10783. 10783   h = 100;
  10784. 10784  
  10785. 10785   t.iframeHTML = s.doctype + '<html><head xmlns="http://www.w3.org/1999/xhtml">';
  10786. 10786  
  10787. 10787   // We only need to override paths if we have to
  10788. 10788   // IE has a bug where it remove site absolute urls to relative ones if this is specified
  10789. 10789   if (s.document_base_url != tinymce.documentBaseURL)
  10790. 10790   t.iframeHTML += '<base href="' + t.documentBaseURI.getURI() + '" />';
  10791. 10791  
  10792. 10792   // IE8 doesn't support carets behind images setting ie7_compat would force IE8+ to run in IE7 compat mode.
  10793. 10793   if (s.ie7_compat)
  10794. 10794   t.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=7" />';
  10795. 10795   else
  10796. 10796   t.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=edge" />';
  10797. 10797  
  10798. 10798   t.iframeHTML += '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />';
  10799. 10799  
  10800. 10800   // Firefox 2 doesn't load stylesheets correctly this way
  10801. 10801   if (!isGecko || !/Firefox\/2/.test(navigator.userAgent)) {
  10802. 10802   for (i = 0; i < t.contentCSS.length; i++)
  10803. 10803   t.iframeHTML += '<link type="text/css" rel="stylesheet" href="' + t.contentCSS[i] + '" />';
  10804. 10804  
  10805. 10805   t.contentCSS = [];
  10806. 10806   }
  10807. 10807  
  10808. 10808   bi = s.body_id || 'tinymce';
  10809. 10809   if (bi.indexOf('=') != -1) {
  10810. 10810   bi = t.getParam('body_id', '', 'hash');
  10811. 10811   bi = bi[t.id] || bi;
  10812. 10812   }
  10813. 10813  
  10814. 10814   bc = s.body_class || '';
  10815. 10815   if (bc.indexOf('=') != -1) {
  10816. 10816   bc = t.getParam('body_class', '', 'hash');
  10817. 10817   bc = bc[t.id] || '';
  10818. 10818   }
  10819. 10819  
  10820. 10820   t.iframeHTML += '</head><body id="' + bi + '" class="mceContentBody ' + bc + '"></body></html>';
  10821. 10821  
  10822. 10822   // Domain relaxing enabled, then set document domain
  10823. 10823   if (tinymce.relaxedDomain && (isIE || (tinymce.isOpera && parseFloat(opera.version()) < 11))) {
  10824. 10824   // We need to write the contents here in IE since multiple writes messes up refresh button and back button
  10825. 10825   u = 'javascript:(function(){document.open();document.domain="' + document.domain + '";var ed = window.parent.tinyMCE.get("' + t.id + '");document.write(ed.iframeHTML);document.close();ed.setupIframe();})()';
  10826. 10826   }
  10827. 10827  
  10828. 10828   // Create iframe
  10829. 10829   // TODO: ACC add the appropriate description on this.
  10830. 10830   n = DOM.add(o.iframeContainer, 'iframe', {
  10831. 10831   id : t.id + "_ifr",
  10832. 10832   src : u || 'javascript:""', // Workaround for HTTPS warning in IE6/7
  10833. 10833   frameBorder : '0',
  10834. 10834   title : s.aria_label,
  10835. 10835   style : {
  10836. 10836   width : '100%',
  10837. 10837   height : h
  10838. 10838   }
  10839. 10839   });
  10840. 10840  
  10841. 10841   t.contentAreaContainer = o.iframeContainer;
  10842. 10842   DOM.get(o.editorContainer).style.display = t.orgDisplay;
  10843. 10843   DOM.get(t.id).style.display = 'none';
  10844. 10844   DOM.setAttrib(t.id, 'aria-hidden', true);
  10845. 10845  
  10846. 10846   if (!tinymce.relaxedDomain || !u)
  10847. 10847   t.setupIframe();
  10848. 10848  
  10849. 10849   e = n = o = null; // Cleanup
  10850. 10850   },
  10851. 10851  
  10852. 10852   setupIframe : function() {
  10853. 10853   var t = this, s = t.settings, e = DOM.get(t.id), d = t.getDoc(), h, b;
  10854. 10854  
  10855. 10855   // Setup iframe body
  10856. 10856   if (!isIE || !tinymce.relaxedDomain) {
  10857. 10857   d.open();
  10858. 10858   d.write(t.iframeHTML);
  10859. 10859   d.close();
  10860. 10860  
  10861. 10861   if (tinymce.relaxedDomain)
  10862. 10862   d.domain = tinymce.relaxedDomain;
  10863. 10863   }
  10864. 10864  
  10865. 10865   // Design mode needs to be added here Ctrl+A will fail otherwise
  10866. 10866   if (!isIE) {
  10867. 10867   try {
  10868. 10868   if (!s.readonly)
  10869. 10869   d.designMode = 'On';
  10870. 10870   } catch (ex) {
  10871. 10871   // Will fail on Gecko if the editor is placed in an hidden container element
  10872. 10872   // The design mode will be set ones the editor is focused
  10873. 10873   }
  10874. 10874   }
  10875. 10875  
  10876. 10876   // IE needs to use contentEditable or it will display non secure items for HTTPS
  10877. 10877   if (isIE) {
  10878. 10878   // It will not steal focus if we hide it while setting contentEditable
  10879. 10879   b = t.getBody();
  10880. 10880   DOM.hide(b);
  10881. 10881  
  10882. 10882   if (!s.readonly)
  10883. 10883   b.contentEditable = true;
  10884. 10884  
  10885. 10885   DOM.show(b);
  10886. 10886   }
  10887. 10887  
  10888. 10888   t.schema = new tinymce.html.Schema(s);
  10889. 10889  
  10890. 10890   t.dom = new tinymce.dom.DOMUtils(t.getDoc(), {
  10891. 10891   keep_values : true,
  10892. 10892   url_converter : t.convertURL,
  10893. 10893   url_converter_scope : t,
  10894. 10894   hex_colors : s.force_hex_style_colors,
  10895. 10895   class_filter : s.class_filter,
  10896. 10896   update_styles : 1,
  10897. 10897   fix_ie_paragraphs : 1,
  10898. 10898   schema : t.schema
  10899. 10899   });
  10900. 10900  
  10901. 10901   t.parser = new tinymce.html.DomParser(s, t.schema);
  10902. 10902  
  10903. 10903   // Force anchor names closed
  10904. 10904   t.parser.addAttributeFilter('name', function(nodes, name) {
  10905. 10905   var i = nodes.length, sibling, prevSibling, parent, node;
  10906. 10906  
  10907. 10907   while (i--) {
  10908. 10908   node = nodes[i];
  10909. 10909   if (node.name === 'a' && node.firstChild) {
  10910. 10910   parent = node.parent;
  10911. 10911  
  10912. 10912   // Move children after current node
  10913. 10913   sibling = node.lastChild;
  10914. 10914   do {
  10915. 10915   prevSibling = sibling.prev;
  10916. 10916   parent.insert(sibling, node);
  10917. 10917   sibling = prevSibling;
  10918. 10918   } while (sibling);
  10919. 10919   }
  10920. 10920   }
  10921. 10921   });
  10922. 10922  
  10923. 10923   // Convert src and href into data-mce-src, data-mce-href and data-mce-style
  10924. 10924   t.parser.addAttributeFilter('src,href,style', function(nodes, name) {
  10925. 10925   var i = nodes.length, node, dom = t.dom, value;
  10926. 10926  
  10927. 10927   while (i--) {
  10928. 10928   node = nodes[i];
  10929. 10929   value = node.attr(name);
  10930. 10930  
  10931. 10931   if (name === "style")
  10932. 10932   node.attr('data-mce-style', dom.serializeStyle(dom.parseStyle(value), node.name));
  10933. 10933   else
  10934. 10934   node.attr('data-mce-' + name, t.convertURL(value, name, node.name));
  10935. 10935   }
  10936. 10936   });
  10937. 10937  
  10938. 10938   // Keep scripts from executing
  10939. 10939   t.parser.addNodeFilter('script', function(nodes, name) {
  10940. 10940   var i = nodes.length;
  10941. 10941  
  10942. 10942   while (i--)
  10943. 10943   nodes[i].attr('type', 'mce-text/javascript');
  10944. 10944   });
  10945. 10945  
  10946. 10946   t.parser.addNodeFilter('#cdata', function(nodes, name) {
  10947. 10947   var i = nodes.length, node;
  10948. 10948  
  10949. 10949   while (i--) {
  10950. 10950   node = nodes[i];
  10951. 10951   node.type = 8;
  10952. 10952   node.name = '#comment';
  10953. 10953   node.value = '[CDATA[' + node.value + ']]';
  10954. 10954   }
  10955. 10955   });
  10956. 10956  
  10957. 10957   t.parser.addNodeFilter('p,h1,h2,h3,h4,h5,h6,div', function(nodes, name) {
  10958. 10958   var i = nodes.length, node, nonEmptyElements = t.schema.getNonEmptyElements();
  10959. 10959  
  10960. 10960   while (i--) {
  10961. 10961   node = nodes[i];
  10962. 10962  
  10963. 10963   if (node.isEmpty(nonEmptyElements))
  10964. 10964   node.empty().append(new tinymce.html.Node('br', 1)).shortEnded = true;
  10965. 10965   }
  10966. 10966   });
  10967. 10967  
  10968. 10968   t.serializer = new tinymce.dom.Serializer(s, t.dom, t.schema);
  10969. 10969  
  10970. 10970   t.selection = new tinymce.dom.Selection(t.dom, t.getWin(), t.serializer);
  10971. 10971  
  10972. 10972   t.formatter = new tinymce.Formatter(this);
  10973. 10973  
  10974. 10974   // Register default formats
  10975. 10975   t.formatter.register({
  10976. 10976   alignleft : [
  10977. 10977   {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'left'}},
  10978. 10978   {selector : 'img,table', collapsed : false, styles : {'float' : 'left'}}
  10979. 10979   ],
  10980. 10980  
  10981. 10981   aligncenter : [
  10982. 10982   {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'center'}},
  10983. 10983   {selector : 'img', collapsed : false, styles : {display : 'block', marginLeft : 'auto', marginRight : 'auto'}},
  10984. 10984   {selector : 'table', collapsed : false, styles : {marginLeft : 'auto', marginRight : 'auto'}}
  10985. 10985   ],
  10986. 10986  
  10987. 10987   alignright : [
  10988. 10988   {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'right'}},
  10989. 10989   {selector : 'img,table', collapsed : false, styles : {'float' : 'right'}}
  10990. 10990   ],
  10991. 10991  
  10992. 10992   alignfull : [
  10993. 10993   {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'justify'}}
  10994. 10994   ],
  10995. 10995  
  10996. 10996   bold : [
  10997. 10997   {inline : 'strong', remove : 'all'},
  10998. 10998   {inline : 'span', styles : {fontWeight : 'bold'}},
  10999. 10999   {inline : 'b', remove : 'all'}
  11000. 11000   ],
  11001. 11001  
  11002. 11002   italic : [
  11003. 11003   {inline : 'em', remove : 'all'},
  11004. 11004   {inline : 'span', styles : {fontStyle : 'italic'}},
  11005. 11005   {inline : 'i', remove : 'all'}
  11006. 11006   ],
  11007. 11007  
  11008. 11008   underline : [
  11009. 11009   {inline : 'span', styles : {textDecoration : 'underline'}, exact : true},
  11010. 11010   {inline : 'u', remove : 'all'}
  11011. 11011   ],
  11012. 11012  
  11013. 11013   strikethrough : [
  11014. 11014   {inline : 'span', styles : {textDecoration : 'line-through'}, exact : true},
  11015. 11015   {inline : 'strike', remove : 'all'}
  11016. 11016   ],
  11017. 11017  
  11018. 11018   forecolor : {inline : 'span', styles : {color : '%value'}, wrap_links : false},
  11019. 11019   hilitecolor : {inline : 'span', styles : {backgroundColor : '%value'}, wrap_links : false},
  11020. 11020   fontname : {inline : 'span', styles : {fontFamily : '%value'}},
  11021. 11021   fontsize : {inline : 'span', styles : {fontSize : '%value'}},
  11022. 11022   fontsize_class : {inline : 'span', attributes : {'class' : '%value'}},
  11023. 11023   blockquote : {block : 'blockquote', wrapper : 1, remove : 'all'},
  11024. 11024   subscript : {inline : 'sub'},
  11025. 11025   superscript : {inline : 'sup'},
  11026. 11026  
  11027. 11027   removeformat : [
  11028. 11028   {selector : 'b,strong,em,i,font,u,strike', remove : 'all', split : true, expand : false, block_expand : true, deep : true},
  11029. 11029   {selector : 'span', attributes : ['style', 'class'], remove : 'empty', split : true, expand : false, deep : true},
  11030. 11030   {selector : '*', attributes : ['style', 'class'], split : false, expand : false, deep : true}
  11031. 11031   ]
  11032. 11032   });
  11033. 11033  
  11034. 11034   // Register default block formats
  11035. 11035   each('p h1 h2 h3 h4 h5 h6 div address pre div code dt dd samp'.split(/\s/), function(name) {
  11036. 11036   t.formatter.register(name, {block : name, remove : 'all'});
  11037. 11037   });
  11038. 11038  
  11039. 11039   // Register user defined formats
  11040. 11040   t.formatter.register(t.settings.formats);
  11041. 11041  
  11042. 11042   t.undoManager = new tinymce.UndoManager(t);
  11043. 11043  
  11044. 11044   // Pass through
  11045. 11045   t.undoManager.onAdd.add(function(um, l) {
  11046. 11046   if (um.hasUndo())
  11047. 11047   return t.onChange.dispatch(t, l, um);
  11048. 11048   });
  11049. 11049  
  11050. 11050   t.undoManager.onUndo.add(function(um, l) {
  11051. 11051   return t.onUndo.dispatch(t, l, um);
  11052. 11052   });
  11053. 11053  
  11054. 11054   t.undoManager.onRedo.add(function(um, l) {
  11055. 11055   return t.onRedo.dispatch(t, l, um);
  11056. 11056   });
  11057. 11057  
  11058. 11058   t.forceBlocks = new tinymce.ForceBlocks(t, {
  11059. 11059   forced_root_block : s.forced_root_block
  11060. 11060   });
  11061. 11061  
  11062. 11062   t.editorCommands = new tinymce.EditorCommands(t);
  11063. 11063  
  11064. 11064   // Pass through
  11065. 11065   t.serializer.onPreProcess.add(function(se, o) {
  11066. 11066   return t.onPreProcess.dispatch(t, o, se);
  11067. 11067   });
  11068. 11068  
  11069. 11069   t.serializer.onPostProcess.add(function(se, o) {
  11070. 11070   return t.onPostProcess.dispatch(t, o, se);
  11071. 11071   });
  11072. 11072  
  11073. 11073   t.onPreInit.dispatch(t);
  11074. 11074  
  11075. 11075   if (!s.gecko_spellcheck)
  11076. 11076   t.getBody().spellcheck = 0;
  11077. 11077  
  11078. 11078   if (!s.readonly)
  11079. 11079   t._addEvents();
  11080. 11080  
  11081. 11081   t.controlManager.onPostRender.dispatch(t, t.controlManager);
  11082. 11082   t.onPostRender.dispatch(t);
  11083. 11083  
  11084. 11084   if (s.directionality)
  11085. 11085   t.getBody().dir = s.directionality;
  11086. 11086  
  11087. 11087   if (s.nowrap)
  11088. 11088   t.getBody().style.whiteSpace = "nowrap";
  11089. 11089  
  11090. 11090   if (s.handle_node_change_callback) {
  11091. 11091   t.onNodeChange.add(function(ed, cm, n) {
  11092. 11092   t.execCallback('handle_node_change_callback', t.id, n, -1, -1, true, t.selection.isCollapsed());
  11093. 11093   });
  11094. 11094   }
  11095. 11095  
  11096. 11096   if (s.save_callback) {
  11097. 11097   t.onSaveContent.add(function(ed, o) {
  11098. 11098   var h = t.execCallback('save_callback', t.id, o.content, t.getBody());
  11099. 11099  
  11100. 11100   if (h)
  11101. 11101   o.content = h;
  11102. 11102   });
  11103. 11103   }
  11104. 11104  
  11105. 11105   if (s.onchange_callback) {
  11106. 11106   t.onChange.add(function(ed, l) {
  11107. 11107   t.execCallback('onchange_callback', t, l);
  11108. 11108   });
  11109. 11109   }
  11110. 11110  
  11111. 11111   if (s.protect) {
  11112. 11112   t.onBeforeSetContent.add(function(ed, o) {
  11113. 11113   if (s.protect) {
  11114. 11114   each(s.protect, function(pattern) {
  11115. 11115   o.content = o.content.replace(pattern, function(str) {
  11116. 11116   return '<!--mce:protected ' + escape(str) + '-->';
  11117. 11117   });
  11118. 11118   });
  11119. 11119   }
  11120. 11120   });
  11121. 11121   }
  11122. 11122  
  11123. 11123   if (s.convert_newlines_to_brs) {
  11124. 11124   t.onBeforeSetContent.add(function(ed, o) {
  11125. 11125   if (o.initial)
  11126. 11126   o.content = o.content.replace(/\r?\n/g, '<br />');
  11127. 11127   });
  11128. 11128   }
  11129. 11129  
  11130. 11130   if (s.preformatted) {
  11131. 11131   t.onPostProcess.add(function(ed, o) {
  11132. 11132   o.content = o.content.replace(/^\s*<pre.*?>/, '');
  11133. 11133   o.content = o.content.replace(/<\/pre>\s*$/, '');
  11134. 11134  
  11135. 11135   if (o.set)
  11136. 11136   o.content = '<pre class="mceItemHidden">' + o.content + '</pre>';
  11137. 11137   });
  11138. 11138   }
  11139. 11139  
  11140. 11140   if (s.verify_css_classes) {
  11141. 11141   t.serializer.attribValueFilter = function(n, v) {
  11142. 11142   var s, cl;
  11143. 11143  
  11144. 11144   if (n == 'class') {
  11145. 11145   // Build regexp for classes
  11146. 11146   if (!t.classesRE) {
  11147. 11147   cl = t.dom.getClasses();
  11148. 11148  
  11149. 11149   if (cl.length > 0) {
  11150. 11150   s = '';
  11151. 11151  
  11152. 11152   each (cl, function(o) {
  11153. 11153   s += (s ? '|' : '') + o['class'];
  11154. 11154   });
  11155. 11155  
  11156. 11156   t.classesRE = new RegExp('(' + s + ')', 'gi');
  11157. 11157   }
  11158. 11158   }
  11159. 11159  
  11160. 11160   return !t.classesRE || /(\bmceItem\w+\b|\bmceTemp\w+\b)/g.test(v) || t.classesRE.test(v) ? v : '';
  11161. 11161   }
  11162. 11162  
  11163. 11163   return v;
  11164. 11164   };
  11165. 11165   }
  11166. 11166  
  11167. 11167   if (s.cleanup_callback) {
  11168. 11168   t.onBeforeSetContent.add(function(ed, o) {
  11169. 11169   o.content = t.execCallback('cleanup_callback', 'insert_to_editor', o.content, o);
  11170. 11170   });
  11171. 11171  
  11172. 11172   t.onPreProcess.add(function(ed, o) {
  11173. 11173   if (o.set)
  11174. 11174   t.execCallback('cleanup_callback', 'insert_to_editor_dom', o.node, o);
  11175. 11175  
  11176. 11176   if (o.get)
  11177. 11177   t.execCallback('cleanup_callback', 'get_from_editor_dom', o.node, o);
  11178. 11178   });
  11179. 11179  
  11180. 11180   t.onPostProcess.add(function(ed, o) {
  11181. 11181   if (o.set)
  11182. 11182   o.content = t.execCallback('cleanup_callback', 'insert_to_editor', o.content, o);
  11183. 11183  
  11184. 11184   if (o.get)
  11185. 11185   o.content = t.execCallback('cleanup_callback', 'get_from_editor', o.content, o);
  11186. 11186   });
  11187. 11187   }
  11188. 11188  
  11189. 11189   if (s.save_callback) {
  11190. 11190   t.onGetContent.add(function(ed, o) {
  11191. 11191   if (o.save)
  11192. 11192   o.content = t.execCallback('save_callback', t.id, o.content, t.getBody());
  11193. 11193   });
  11194. 11194   }
  11195. 11195  
  11196. 11196   if (s.handle_event_callback) {
  11197. 11197   t.onEvent.add(function(ed, e, o) {
  11198. 11198   if (t.execCallback('handle_event_callback', e, ed, o) === false)
  11199. 11199   Event.cancel(e);
  11200. 11200   });
  11201. 11201   }
  11202. 11202  
  11203. 11203   // Add visual aids when new contents is added
  11204. 11204   t.onSetContent.add(function() {
  11205. 11205   t.addVisual(t.getBody());
  11206. 11206   });
  11207. 11207  
  11208. 11208   // Remove empty contents
  11209. 11209   if (s.padd_empty_editor) {
  11210. 11210   t.onPostProcess.add(function(ed, o) {
  11211. 11211   o.content = o.content.replace(/^(<p[^>]*>(&nbsp;|&#160;|\s|\u00a0|)<\/p>[\r\n]*|<br \/>[\r\n]*)$/, '');
  11212. 11212   });
  11213. 11213   }
  11214. 11214  
  11215. 11215   if (isGecko) {
  11216. 11216   // Fix gecko link bug, when a link is placed at the end of block elements there is
  11217. 11217   // no way to move the caret behind the link. This fix adds a bogus br element after the link
  11218. 11218   function fixLinks(ed, o) {
  11219. 11219   each(ed.dom.select('a'), function(n) {
  11220. 11220   var pn = n.parentNode;
  11221. 11221  
  11222. 11222   if (ed.dom.isBlock(pn) && pn.lastChild === n)
  11223. 11223   ed.dom.add(pn, 'br', {'data-mce-bogus' : 1});
  11224. 11224   });
  11225. 11225   };
  11226. 11226  
  11227. 11227   t.onExecCommand.add(function(ed, cmd) {
  11228. 11228   if (cmd === 'CreateLink')
  11229. 11229   fixLinks(ed);
  11230. 11230   });
  11231. 11231  
  11232. 11232   t.onSetContent.add(t.selection.onSetContent.add(fixLinks));
  11233. 11233  
  11234. 11234   if (!s.readonly) {
  11235. 11235   try {
  11236. 11236   // Design mode must be set here once again to fix a bug where
  11237. 11237   // Ctrl+A/Delete/Backspace didn't work if the editor was added using mceAddControl then removed then added again
  11238. 11238   d.designMode = 'Off';
  11239. 11239   d.designMode = 'On';
  11240. 11240   } catch (ex) {
  11241. 11241   // Will fail on Gecko if the editor is placed in an hidden container element
  11242. 11242   // The design mode will be set ones the editor is focused
  11243. 11243   }
  11244. 11244   }
  11245. 11245   }
  11246. 11246  
  11247. 11247   // A small timeout was needed since firefox will remove. Bug: #1838304
  11248. 11248   setTimeout(function () {
  11249. 11249   if (t.removed)
  11250. 11250   return;
  11251. 11251  
  11252. 11252   t.load({initial : true, format : 'html'});
  11253. 11253   t.startContent = t.getContent({format : 'raw'});
  11254. 11254   t.undoManager.add();
  11255. 11255   t.initialized = true;
  11256. 11256  
  11257. 11257   t.onInit.dispatch(t);
  11258. 11258   t.execCallback('setupcontent_callback', t.id, t.getBody(), t.getDoc());
  11259. 11259   t.execCallback('init_instance_callback', t);
  11260. 11260   t.focus(true);
  11261. 11261   t.nodeChanged({initial : 1});
  11262. 11262  
  11263. 11263   // Load specified content CSS last
  11264. 11264   each(t.contentCSS, function(u) {
  11265. 11265   t.dom.loadCSS(u);
  11266. 11266   });
  11267. 11267  
  11268. 11268   // Handle auto focus
  11269. 11269   if (s.auto_focus) {
  11270. 11270   setTimeout(function () {
  11271. 11271   var ed = tinymce.get(s.auto_focus);
  11272. 11272  
  11273. 11273   ed.selection.select(ed.getBody(), 1);
  11274. 11274   ed.selection.collapse(1);
  11275. 11275   ed.getWin().focus();
  11276. 11276   }, 100);
  11277. 11277   }
  11278. 11278   }, 1);
  11279. 11279  
  11280. 11280   e = null;
  11281. 11281   },
  11282. 11282  
  11283. 11283  
  11284. 11284   focus : function(sf) {
  11285. 11285   var oed, t = this, ce = t.settings.content_editable, ieRng, controlElm, doc = t.getDoc();
  11286. 11286  
  11287. 11287   if (!sf) {
  11288. 11288   // Get selected control element
  11289. 11289   ieRng = t.selection.getRng();
  11290. 11290   if (ieRng.item) {
  11291. 11291   controlElm = ieRng.item(0);
  11292. 11292   }
  11293. 11293  
  11294. 11294   // Is not content editable
  11295. 11295   if (!ce)
  11296. 11296   t.getWin().focus();
  11297. 11297  
  11298. 11298   // Restore selected control element
  11299. 11299   // This is needed when for example an image is selected within a
  11300. 11300   // layer a call to focus will then remove the control selection
  11301. 11301   if (controlElm && controlElm.ownerDocument == doc) {
  11302. 11302   ieRng = doc.body.createControlRange();
  11303. 11303   ieRng.addElement(controlElm);
  11304. 11304   ieRng.select();
  11305. 11305   }
  11306. 11306  
  11307. 11307   }
  11308. 11308  
  11309. 11309   if (tinymce.activeEditor != t) {
  11310. 11310   if ((oed = tinymce.activeEditor) != null)
  11311. 11311   oed.onDeactivate.dispatch(oed, t);
  11312. 11312  
  11313. 11313   t.onActivate.dispatch(t, oed);
  11314. 11314   }
  11315. 11315  
  11316. 11316   tinymce._setActive(t);
  11317. 11317   },
  11318. 11318  
  11319. 11319   execCallback : function(n) {
  11320. 11320   var t = this, f = t.settings[n], s;
  11321. 11321  
  11322. 11322   if (!f)
  11323. 11323   return;
  11324. 11324  
  11325. 11325   // Look through lookup
  11326. 11326   if (t.callbackLookup && (s = t.callbackLookup[n])) {
  11327. 11327   f = s.func;
  11328. 11328   s = s.scope;
  11329. 11329   }
  11330. 11330  
  11331. 11331   if (is(f, 'string')) {
  11332. 11332   s = f.replace(/\.\w+$/, '');
  11333. 11333   s = s ? tinymce.resolve(s) : 0;
  11334. 11334   f = tinymce.resolve(f);
  11335. 11335   t.callbackLookup = t.callbackLookup || {};
  11336. 11336   t.callbackLookup[n] = {func : f, scope : s};
  11337. 11337   }
  11338. 11338  
  11339. 11339   return f.apply(s || t, Array.prototype.slice.call(arguments, 1));
  11340. 11340   },
  11341. 11341  
  11342. 11342   translate : function(s) {
  11343. 11343   var c = this.settings.language || 'en', i18n = tinymce.i18n;
  11344. 11344  
  11345. 11345   if (!s)
  11346. 11346   return '';
  11347. 11347  
  11348. 11348   return i18n[c + '.' + s] || s.replace(/{\#([^}]+)\}/g, function(a, b) {
  11349. 11349   return i18n[c + '.' + b] || '{#' + b + '}';
  11350. 11350   });
  11351. 11351   },
  11352. 11352  
  11353. 11353   getLang : function(n, dv) {
  11354. 11354   return tinymce.i18n[(this.settings.language || 'en') + '.' + n] || (is(dv) ? dv : '{#' + n + '}');
  11355. 11355   },
  11356. 11356  
  11357. 11357   getParam : function(n, dv, ty) {
  11358. 11358   var tr = tinymce.trim, v = is(this.settings[n]) ? this.settings[n] : dv, o;
  11359. 11359  
  11360. 11360   if (ty === 'hash') {
  11361. 11361   o = {};
  11362. 11362  
  11363. 11363   if (is(v, 'string')) {
  11364. 11364   each(v.indexOf('=') > 0 ? v.split(/[;,](?![^=;,]*(?:[;,]|$))/) : v.split(','), function(v) {
  11365. 11365   v = v.split('=');
  11366. 11366  
  11367. 11367   if (v.length > 1)
  11368. 11368   o[tr(v[0])] = tr(v[1]);
  11369. 11369   else
  11370. 11370   o[tr(v[0])] = tr(v);
  11371. 11371   });
  11372. 11372   } else
  11373. 11373   o = v;
  11374. 11374  
  11375. 11375   return o;
  11376. 11376   }
  11377. 11377  
  11378. 11378   return v;
  11379. 11379   },
  11380. 11380  
  11381. 11381   nodeChanged : function(o) {
  11382. 11382   var t = this, s = t.selection, n = s.getStart() || t.getBody();
  11383. 11383  
  11384. 11384   // Fix for bug #1896577 it seems that this can not be fired while the editor is loading
  11385. 11385   if (t.initialized) {
  11386. 11386   o = o || {};
  11387. 11387   n = isIE && n.ownerDocument != t.getDoc() ? t.getBody() : n; // Fix for IE initial state
  11388. 11388  
  11389. 11389   // Get parents and add them to object
  11390. 11390   o.parents = [];
  11391. 11391   t.dom.getParent(n, function(node) {
  11392. 11392   if (node.nodeName == 'BODY')
  11393. 11393   return true;
  11394. 11394  
  11395. 11395   o.parents.push(node);
  11396. 11396   });
  11397. 11397  
  11398. 11398   t.onNodeChange.dispatch(
  11399. 11399   t,
  11400. 11400   o ? o.controlManager || t.controlManager : t.controlManager,
  11401. 11401   n,
  11402. 11402   s.isCollapsed(),
  11403. 11403   o
  11404. 11404   );
  11405. 11405   }
  11406. 11406   },
  11407. 11407  
  11408. 11408   addButton : function(n, s) {
  11409. 11409   var t = this;
  11410. 11410  
  11411. 11411   t.buttons = t.buttons || {};
  11412. 11412   t.buttons[n] = s;
  11413. 11413   },
  11414. 11414  
  11415. 11415   addCommand : function(name, callback, scope) {
  11416. 11416   this.execCommands[name] = {func : callback, scope : scope || this};
  11417. 11417   },
  11418. 11418  
  11419. 11419   addQueryStateHandler : function(name, callback, scope) {
  11420. 11420   this.queryStateCommands[name] = {func : callback, scope : scope || this};
  11421. 11421   },
  11422. 11422  
  11423. 11423   addQueryValueHandler : function(name, callback, scope) {
  11424. 11424   this.queryValueCommands[name] = {func : callback, scope : scope || this};
  11425. 11425   },
  11426. 11426  
  11427. 11427   addShortcut : function(pa, desc, cmd_func, sc) {
  11428. 11428   var t = this, c;
  11429. 11429  
  11430. 11430   if (!t.settings.custom_shortcuts)
  11431. 11431   return false;
  11432. 11432  
  11433. 11433   t.shortcuts = t.shortcuts || {};
  11434. 11434  
  11435. 11435   if (is(cmd_func, 'string')) {
  11436. 11436   c = cmd_func;
  11437. 11437  
  11438. 11438   cmd_func = function() {
  11439. 11439   t.execCommand(c, false, null);
  11440. 11440   };
  11441. 11441   }
  11442. 11442  
  11443. 11443   if (is(cmd_func, 'object')) {
  11444. 11444   c = cmd_func;
  11445. 11445  
  11446. 11446   cmd_func = function() {
  11447. 11447   t.execCommand(c[0], c[1], c[2]);
  11448. 11448   };
  11449. 11449   }
  11450. 11450  
  11451. 11451   each(explode(pa), function(pa) {
  11452. 11452   var o = {
  11453. 11453   func : cmd_func,
  11454. 11454   scope : sc || this,
  11455. 11455   desc : desc,
  11456. 11456   alt : false,
  11457. 11457   ctrl : false,
  11458. 11458   shift : false
  11459. 11459   };
  11460. 11460  
  11461. 11461   each(explode(pa, '+'), function(v) {
  11462. 11462   switch (v) {
  11463. 11463   case 'alt':
  11464. 11464   case 'ctrl':
  11465. 11465   case 'shift':
  11466. 11466   o[v] = true;
  11467. 11467   break;
  11468. 11468  
  11469. 11469   default:
  11470. 11470   o.charCode = v.charCodeAt(0);
  11471. 11471   o.keyCode = v.toUpperCase().charCodeAt(0);
  11472. 11472   }
  11473. 11473   });
  11474. 11474  
  11475. 11475   t.shortcuts[(o.ctrl ? 'ctrl' : '') + ',' + (o.alt ? 'alt' : '') + ',' + (o.shift ? 'shift' : '') + ',' + o.keyCode] = o;
  11476. 11476   });
  11477. 11477  
  11478. 11478   return true;
  11479. 11479   },
  11480. 11480  
  11481. 11481   execCommand : function(cmd, ui, val, a) {
  11482. 11482   var t = this, s = 0, o, st;
  11483. 11483  
  11484. 11484   if (!/^(mceAddUndoLevel|mceEndUndoLevel|mceBeginUndoLevel|mceRepaint|SelectAll)$/.test(cmd) && (!a || !a.skip_focus))
  11485. 11485   t.focus();
  11486. 11486  
  11487. 11487   o = {};
  11488. 11488   t.onBeforeExecCommand.dispatch(t, cmd, ui, val, o);
  11489. 11489   if (o.terminate)
  11490. 11490   return false;
  11491. 11491  
  11492. 11492   // Command callback
  11493. 11493   if (t.execCallback('execcommand_callback', t.id, t.selection.getNode(), cmd, ui, val)) {
  11494. 11494   t.onExecCommand.dispatch(t, cmd, ui, val, a);
  11495. 11495   return true;
  11496. 11496   }
  11497. 11497  
  11498. 11498   // Registred commands
  11499. 11499   if (o = t.execCommands[cmd]) {
  11500. 11500   st = o.func.call(o.scope, ui, val);
  11501. 11501  
  11502. 11502   // Fall through on true
  11503. 11503   if (st !== true) {
  11504. 11504   t.onExecCommand.dispatch(t, cmd, ui, val, a);
  11505. 11505   return st;
  11506. 11506   }
  11507. 11507   }
  11508. 11508  
  11509. 11509   // Plugin commands
  11510. 11510   each(t.plugins, function(p) {
  11511. 11511   if (p.execCommand && p.execCommand(cmd, ui, val)) {
  11512. 11512   t.onExecCommand.dispatch(t, cmd, ui, val, a);
  11513. 11513   s = 1;
  11514. 11514   return false;
  11515. 11515   }
  11516. 11516   });
  11517. 11517  
  11518. 11518   if (s)
  11519. 11519   return true;
  11520. 11520  
  11521. 11521   // Theme commands
  11522. 11522   if (t.theme && t.theme.execCommand && t.theme.execCommand(cmd, ui, val)) {
  11523. 11523   t.onExecCommand.dispatch(t, cmd, ui, val, a);
  11524. 11524   return true;
  11525. 11525   }
  11526. 11526  
  11527. 11527   // Editor commands
  11528. 11528   if (t.editorCommands.execCommand(cmd, ui, val)) {
  11529. 11529   t.onExecCommand.dispatch(t, cmd, ui, val, a);
  11530. 11530   return true;
  11531. 11531   }
  11532. 11532  
  11533. 11533   // Browser commands
  11534. 11534   t.getDoc().execCommand(cmd, ui, val);
  11535. 11535   t.onExecCommand.dispatch(t, cmd, ui, val, a);
  11536. 11536   },
  11537. 11537  
  11538. 11538   queryCommandState : function(cmd) {
  11539. 11539   var t = this, o, s;
  11540. 11540  
  11541. 11541   // Is hidden then return undefined
  11542. 11542   if (t._isHidden())
  11543. 11543   return;
  11544. 11544  
  11545. 11545   // Registred commands
  11546. 11546   if (o = t.queryStateCommands[cmd]) {
  11547. 11547   s = o.func.call(o.scope);
  11548. 11548  
  11549. 11549   // Fall though on true
  11550. 11550   if (s !== true)
  11551. 11551   return s;
  11552. 11552   }
  11553. 11553  
  11554. 11554   // Registred commands
  11555. 11555   o = t.editorCommands.queryCommandState(cmd);
  11556. 11556   if (o !== -1)
  11557. 11557   return o;
  11558. 11558  
  11559. 11559   // Browser commands
  11560. 11560   try {
  11561. 11561   return this.getDoc().queryCommandState(cmd);
  11562. 11562   } catch (ex) {
  11563. 11563   // Fails sometimes see bug: 1896577
  11564. 11564   }
  11565. 11565   },
  11566. 11566  
  11567. 11567   queryCommandValue : function(c) {
  11568. 11568   var t = this, o, s;
  11569. 11569  
  11570. 11570   // Is hidden then return undefined
  11571. 11571   if (t._isHidden())
  11572. 11572   return;
  11573. 11573  
  11574. 11574   // Registred commands
  11575. 11575   if (o = t.queryValueCommands[c]) {
  11576. 11576   s = o.func.call(o.scope);
  11577. 11577  
  11578. 11578   // Fall though on true
  11579. 11579   if (s !== true)
  11580. 11580   return s;
  11581. 11581   }
  11582. 11582  
  11583. 11583   // Registred commands
  11584. 11584   o = t.editorCommands.queryCommandValue(c);
  11585. 11585   if (is(o))
  11586. 11586   return o;
  11587. 11587  
  11588. 11588   // Browser commands
  11589. 11589   try {
  11590. 11590   return this.getDoc().queryCommandValue(c);
  11591. 11591   } catch (ex) {
  11592. 11592   // Fails sometimes see bug: 1896577
  11593. 11593   }
  11594. 11594   },
  11595. 11595  
  11596. 11596   show : function() {
  11597. 11597   var t = this;
  11598. 11598  
  11599. 11599   DOM.show(t.getContainer());
  11600. 11600   DOM.hide(t.id);
  11601. 11601   t.load();
  11602. 11602   },
  11603. 11603  
  11604. 11604   hide : function() {
  11605. 11605   var t = this, d = t.getDoc();
  11606. 11606  
  11607. 11607   // Fixed bug where IE has a blinking cursor left from the editor
  11608. 11608   if (isIE && d)
  11609. 11609   d.execCommand('SelectAll');
  11610. 11610  
  11611. 11611   // We must save before we hide so Safari doesn't crash
  11612. 11612   t.save();
  11613. 11613   DOM.hide(t.getContainer());
  11614. 11614   DOM.setStyle(t.id, 'display', t.orgDisplay);
  11615. 11615   },
  11616. 11616  
  11617. 11617   isHidden : function() {
  11618. 11618   return !DOM.isHidden(this.id);
  11619. 11619   },
  11620. 11620  
  11621. 11621   setProgressState : function(b, ti, o) {
  11622. 11622   this.onSetProgressState.dispatch(this, b, ti, o);
  11623. 11623  
  11624. 11624   return b;
  11625. 11625   },
  11626. 11626  
  11627. 11627   load : function(o) {
  11628. 11628   var t = this, e = t.getElement(), h;
  11629. 11629  
  11630. 11630   if (e) {
  11631. 11631   o = o || {};
  11632. 11632   o.load = true;
  11633. 11633  
  11634. 11634   // Double encode existing entities in the value
  11635. 11635   h = t.setContent(is(e.value) ? e.value : e.innerHTML, o);
  11636. 11636   o.element = e;
  11637. 11637  
  11638. 11638   if (!o.no_events)
  11639. 11639   t.onLoadContent.dispatch(t, o);
  11640. 11640  
  11641. 11641   o.element = e = null;
  11642. 11642  
  11643. 11643   return h;
  11644. 11644   }
  11645. 11645   },
  11646. 11646  
  11647. 11647   save : function(o) {
  11648. 11648   var t = this, e = t.getElement(), h, f;
  11649. 11649  
  11650. 11650   if (!e || !t.initialized)
  11651. 11651   return;
  11652. 11652  
  11653. 11653   o = o || {};
  11654. 11654   o.save = true;
  11655. 11655  
  11656. 11656   // Add undo level will trigger onchange event
  11657. 11657   if (!o.no_events) {
  11658. 11658   t.undoManager.typing = false;
  11659. 11659   t.undoManager.add();
  11660. 11660   }
  11661. 11661  
  11662. 11662   o.element = e;
  11663. 11663   h = o.content = t.getContent(o);
  11664. 11664  
  11665. 11665   if (!o.no_events)
  11666. 11666   t.onSaveContent.dispatch(t, o);
  11667. 11667  
  11668. 11668   h = o.content;
  11669. 11669  
  11670. 11670   if (!/TEXTAREA|INPUT/i.test(e.nodeName)) {
  11671. 11671   e.innerHTML = h;
  11672. 11672  
  11673. 11673   // Update hidden form element
  11674. 11674   if (f = DOM.getParent(t.id, 'form')) {
  11675. 11675   each(f.elements, function(e) {
  11676. 11676   if (e.name == t.id) {
  11677. 11677   e.value = h;
  11678. 11678   return false;
  11679. 11679   }
  11680. 11680   });
  11681. 11681   }
  11682. 11682   } else
  11683. 11683   e.value = h;
  11684. 11684  
  11685. 11685   o.element = e = null;
  11686. 11686  
  11687. 11687   return h;
  11688. 11688   },
  11689. 11689  
  11690. 11690   setContent : function(content, args) {
  11691. 11691   var self = this, rootNode, body = self.getBody();
  11692. 11692  
  11693. 11693   // Setup args object
  11694. 11694   args = args || {};
  11695. 11695   args.format = args.format || 'html';
  11696. 11696   args.set = true;
  11697. 11697   args.content = content;
  11698. 11698  
  11699. 11699   // Do preprocessing
  11700. 11700   if (!args.no_events)
  11701. 11701   self.onBeforeSetContent.dispatch(self, args);
  11702. 11702  
  11703. 11703   content = args.content;
  11704. 11704  
  11705. 11705   // Padd empty content in Gecko and Safari. Commands will otherwise fail on the content
  11706. 11706   // It will also be impossible to place the caret in the editor unless there is a BR element present
  11707. 11707   if (!tinymce.isIE && (content.length === 0 || /^\s+$/.test(content))) {
  11708. 11708   body.innerHTML = '<br data-mce-bogus="1" />';
  11709. 11709   return;
  11710. 11710   }
  11711. 11711  
  11712. 11712   // Parse and serialize the html
  11713. 11713   if (args.format !== 'raw') {
  11714. 11714   content = new tinymce.html.Serializer({}, self.schema).serialize(
  11715. 11715   self.parser.parse(content)
  11716. 11716   );
  11717. 11717   }
  11718. 11718  
  11719. 11719   // Set the new cleaned contents to the editor
  11720. 11720   args.content = tinymce.trim(content);
  11721. 11721   self.dom.setHTML(body, args.content);
  11722. 11722  
  11723. 11723   // Do post processing
  11724. 11724   if (!args.no_events)
  11725. 11725   self.onSetContent.dispatch(self, args);
  11726. 11726  
  11727. 11727   return args.content;
  11728. 11728   },
  11729. 11729  
  11730. 11730   getContent : function(args) {
  11731. 11731   var self = this, content;
  11732. 11732  
  11733. 11733   // Setup args object
  11734. 11734   args = args || {};
  11735. 11735   args.format = args.format || 'html';
  11736. 11736   args.get = true;
  11737. 11737  
  11738. 11738   // Do preprocessing
  11739. 11739   if (!args.no_events)
  11740. 11740   self.onBeforeGetContent.dispatch(self, args);
  11741. 11741  
  11742. 11742   // Get raw contents or by default the cleaned contents
  11743. 11743   if (args.format == 'raw')
  11744. 11744   content = self.getBody().innerHTML;
  11745. 11745   else
  11746. 11746   content = self.serializer.serialize(self.getBody(), args);
  11747. 11747  
  11748. 11748   args.content = tinymce.trim(content);
  11749. 11749  
  11750. 11750   // Do post processing
  11751. 11751   if (!args.no_events)
  11752. 11752   self.onGetContent.dispatch(self, args);
  11753. 11753  
  11754. 11754   return args.content;
  11755. 11755   },
  11756. 11756  
  11757. 11757   isDirty : function() {
  11758. 11758   var self = this;
  11759. 11759  
  11760. 11760   return tinymce.trim(self.startContent) != tinymce.trim(self.getContent({format : 'raw', no_events : 1})) && !self.isNotDirty;
  11761. 11761   },
  11762. 11762  
  11763. 11763   getContainer : function() {
  11764. 11764   var t = this;
  11765. 11765  
  11766. 11766   if (!t.container)
  11767. 11767   t.container = DOM.get(t.editorContainer || t.id + '_parent');
  11768. 11768  
  11769. 11769   return t.container;
  11770. 11770   },
  11771. 11771  
  11772. 11772   getContentAreaContainer : function() {
  11773. 11773   return this.contentAreaContainer;
  11774. 11774   },
  11775. 11775  
  11776. 11776   getElement : function() {
  11777. 11777   return DOM.get(this.settings.content_element || this.id);
  11778. 11778   },
  11779. 11779  
  11780. 11780   getWin : function() {
  11781. 11781   var t = this, e;
  11782. 11782  
  11783. 11783   if (!t.contentWindow) {
  11784. 11784   e = DOM.get(t.id + "_ifr");
  11785. 11785  
  11786. 11786   if (e)
  11787. 11787   t.contentWindow = e.contentWindow;
  11788. 11788   }
  11789. 11789  
  11790. 11790   return t.contentWindow;
  11791. 11791   },
  11792. 11792  
  11793. 11793   getDoc : function() {
  11794. 11794   var t = this, w;
  11795. 11795  
  11796. 11796   if (!t.contentDocument) {
  11797. 11797   w = t.getWin();
  11798. 11798  
  11799. 11799   if (w)
  11800. 11800   t.contentDocument = w.document;
  11801. 11801   }
  11802. 11802  
  11803. 11803   return t.contentDocument;
  11804. 11804   },
  11805. 11805  
  11806. 11806   getBody : function() {
  11807. 11807   return this.bodyElement || this.getDoc().body;
  11808. 11808   },
  11809. 11809  
  11810. 11810   convertURL : function(u, n, e) {
  11811. 11811   var t = this, s = t.settings;
  11812. 11812  
  11813. 11813   // Use callback instead
  11814. 11814   if (s.urlconverter_callback)
  11815. 11815   return t.execCallback('urlconverter_callback', u, e, true, n);
  11816. 11816  
  11817. 11817   // Don't convert link href since thats the CSS files that gets loaded into the editor also skip local file URLs
  11818. 11818   if (!s.convert_urls || (e && e.nodeName == 'LINK') || u.indexOf('file:') === 0)
  11819. 11819   return u;
  11820. 11820  
  11821. 11821   // Convert to relative
  11822. 11822   if (s.relative_urls)
  11823. 11823   return t.documentBaseURI.toRelative(u);
  11824. 11824  
  11825. 11825   // Convert to absolute
  11826. 11826   u = t.documentBaseURI.toAbsolute(u, s.remove_script_host);
  11827. 11827  
  11828. 11828   return u;
  11829. 11829   },
  11830. 11830  
  11831. 11831   addVisual : function(e) {
  11832. 11832   var t = this, s = t.settings;
  11833. 11833  
  11834. 11834   e = e || t.getBody();
  11835. 11835  
  11836. 11836   if (!is(t.hasVisual))
  11837. 11837   t.hasVisual = s.visual;
  11838. 11838  
  11839. 11839   each(t.dom.select('table,a', e), function(e) {
  11840. 11840   var v;
  11841. 11841  
  11842. 11842   switch (e.nodeName) {
  11843. 11843   case 'TABLE':
  11844. 11844   v = t.dom.getAttrib(e, 'border');
  11845. 11845  
  11846. 11846   if (!v || v == '0') {
  11847. 11847   if (t.hasVisual)
  11848. 11848   t.dom.addClass(e, s.visual_table_class);
  11849. 11849   else
  11850. 11850   t.dom.removeClass(e, s.visual_table_class);
  11851. 11851   }
  11852. 11852  
  11853. 11853   return;
  11854. 11854  
  11855. 11855   case 'A':
  11856. 11856   v = t.dom.getAttrib(e, 'name');
  11857. 11857  
  11858. 11858   if (v) {
  11859. 11859   if (t.hasVisual)
  11860. 11860   t.dom.addClass(e, 'mceItemAnchor');
  11861. 11861   else
  11862. 11862   t.dom.removeClass(e, 'mceItemAnchor');
  11863. 11863   }
  11864. 11864  
  11865. 11865   return;
  11866. 11866   }
  11867. 11867   });
  11868. 11868  
  11869. 11869   t.onVisualAid.dispatch(t, e, t.hasVisual);
  11870. 11870   },
  11871. 11871  
  11872. 11872   remove : function() {
  11873. 11873   var t = this, e = t.getContainer();
  11874. 11874  
  11875. 11875   t.removed = 1; // Cancels post remove event execution
  11876. 11876   t.hide();
  11877. 11877  
  11878. 11878   t.execCallback('remove_instance_callback', t);
  11879. 11879   t.onRemove.dispatch(t);
  11880. 11880  
  11881. 11881   // Clear all execCommand listeners this is required to avoid errors if the editor was removed inside another command
  11882. 11882   t.onExecCommand.listeners = [];
  11883. 11883  
  11884. 11884   tinymce.remove(t);
  11885. 11885   DOM.remove(e);
  11886. 11886   },
  11887. 11887  
  11888. 11888   destroy : function(s) {
  11889. 11889   var t = this;
  11890. 11890  
  11891. 11891   // One time is enough
  11892. 11892   if (t.destroyed)
  11893. 11893   return;
  11894. 11894  
  11895. 11895   if (!s) {
  11896. 11896   tinymce.removeUnload(t.destroy);
  11897. 11897   tinyMCE.onBeforeUnload.remove(t._beforeUnload);
  11898. 11898  
  11899. 11899   // Manual destroy
  11900. 11900   if (t.theme && t.theme.destroy)
  11901. 11901   t.theme.destroy();
  11902. 11902  
  11903. 11903   // Destroy controls, selection and dom
  11904. 11904   t.controlManager.destroy();
  11905. 11905   t.selection.destroy();
  11906. 11906   t.dom.destroy();
  11907. 11907  
  11908. 11908   // Remove all events
  11909. 11909  
  11910. 11910   // Don't clear the window or document if content editable
  11911. 11911   // is enabled since other instances might still be present
  11912. 11912   if (!t.settings.content_editable) {
  11913. 11913   Event.clear(t.getWin());
  11914. 11914   Event.clear(t.getDoc());
  11915. 11915   }
  11916. 11916  
  11917. 11917   Event.clear(t.getBody());
  11918. 11918   Event.clear(t.formElement);
  11919. 11919   }
  11920. 11920  
  11921. 11921   if (t.formElement) {
  11922. 11922   t.formElement.submit = t.formElement._mceOldSubmit;
  11923. 11923   t.formElement._mceOldSubmit = null;
  11924. 11924   }
  11925. 11925  
  11926. 11926   t.contentAreaContainer = t.formElement = t.container = t.settings.content_element = t.bodyElement = t.contentDocument = t.contentWindow = null;
  11927. 11927  
  11928. 11928   if (t.selection)
  11929. 11929   t.selection = t.selection.win = t.selection.dom = t.selection.dom.doc = null;
  11930. 11930  
  11931. 11931   t.destroyed = 1;
  11932. 11932   },
  11933. 11933  
  11934. 11934   // Internal functions
  11935. 11935  
  11936. 11936   _addEvents : function() {
  11937. 11937   // 'focus', 'blur', 'dblclick', 'beforedeactivate', submit, reset
  11938. 11938   var t = this, i, s = t.settings, dom = t.dom, lo = {
  11939. 11939   mouseup : 'onMouseUp',
  11940. 11940   mousedown : 'onMouseDown',
  11941. 11941   click : 'onClick',
  11942. 11942   keyup : 'onKeyUp',
  11943. 11943   keydown : 'onKeyDown',
  11944. 11944   keypress : 'onKeyPress',
  11945. 11945   submit : 'onSubmit',
  11946. 11946   reset : 'onReset',
  11947. 11947   contextmenu : 'onContextMenu',
  11948. 11948   dblclick : 'onDblClick',
  11949. 11949   paste : 'onPaste' // Doesn't work in all browsers yet
  11950. 11950   };
  11951. 11951  
  11952. 11952   function eventHandler(e, o) {
  11953. 11953   var ty = e.type;
  11954. 11954  
  11955. 11955   // Don't fire events when it's removed
  11956. 11956   if (t.removed)
  11957. 11957   return;
  11958. 11958  
  11959. 11959   // Generic event handler
  11960. 11960   if (t.onEvent.dispatch(t, e, o) !== false) {
  11961. 11961   // Specific event handler
  11962. 11962   t[lo[e.fakeType || e.type]].dispatch(t, e, o);
  11963. 11963   }
  11964. 11964   };
  11965. 11965  
  11966. 11966   // Add DOM events
  11967. 11967   each(lo, function(v, k) {
  11968. 11968   switch (k) {
  11969. 11969   case 'contextmenu':
  11970. 11970   dom.bind(t.getDoc(), k, eventHandler);
  11971. 11971   break;
  11972. 11972  
  11973. 11973   case 'paste':
  11974. 11974   dom.bind(t.getBody(), k, function(e) {
  11975. 11975   eventHandler(e);
  11976. 11976   });
  11977. 11977   break;
  11978. 11978  
  11979. 11979   case 'submit':
  11980. 11980   case 'reset':
  11981. 11981   dom.bind(t.getElement().form || DOM.getParent(t.id, 'form'), k, eventHandler);
  11982. 11982   break;
  11983. 11983  
  11984. 11984   default:
  11985. 11985   dom.bind(s.content_editable ? t.getBody() : t.getDoc(), k, eventHandler);
  11986. 11986   }
  11987. 11987   });
  11988. 11988  
  11989. 11989   dom.bind(s.content_editable ? t.getBody() : (isGecko ? t.getDoc() : t.getWin()), 'focus', function(e) {
  11990. 11990   t.focus(true);
  11991. 11991   });
  11992. 11992  
  11993. 11993  
  11994. 11994   // Fixes bug where a specified document_base_uri could result in broken images
  11995. 11995   // This will also fix drag drop of images in Gecko
  11996. 11996   if (tinymce.isGecko) {
  11997. 11997   dom.bind(t.getDoc(), 'DOMNodeInserted', function(e) {
  11998. 11998   var v;
  11999. 11999  
  12000. 12000   e = e.target;
  12001. 12001  
  12002. 12002   if (e.nodeType === 1 && e.nodeName === 'IMG' && (v = e.getAttribute('data-mce-src')))
  12003. 12003   e.src = t.documentBaseURI.toAbsolute(v);
  12004. 12004   });
  12005. 12005   }
  12006. 12006  
  12007. 12007   // Set various midas options in Gecko
  12008. 12008   if (isGecko) {
  12009. 12009   function setOpts() {
  12010. 12010   var t = this, d = t.getDoc(), s = t.settings;
  12011. 12011  
  12012. 12012   if (isGecko && !s.readonly) {
  12013. 12013   if (t._isHidden()) {
  12014. 12014   try {
  12015. 12015   if (!s.content_editable)
  12016. 12016   d.designMode = 'On';
  12017. 12017   } catch (ex) {
  12018. 12018   // Fails if it's hidden
  12019. 12019   }
  12020. 12020   }
  12021. 12021  
  12022. 12022   try {
  12023. 12023   // Try new Gecko method
  12024. 12024   d.execCommand("styleWithCSS", 0, false);
  12025. 12025   } catch (ex) {
  12026. 12026   // Use old method
  12027. 12027   if (!t._isHidden())
  12028. 12028   try {d.execCommand("useCSS", 0, true);} catch (ex) {}
  12029. 12029   }
  12030. 12030  
  12031. 12031   if (!s.table_inline_editing)
  12032. 12032   try {d.execCommand('enableInlineTableEditing', false, false);} catch (ex) {}
  12033. 12033  
  12034. 12034   if (!s.object_resizing)
  12035. 12035   try {d.execCommand('enableObjectResizing', false, false);} catch (ex) {}
  12036. 12036   }
  12037. 12037   };
  12038. 12038  
  12039. 12039   t.onBeforeExecCommand.add(setOpts);
  12040. 12040   t.onMouseDown.add(setOpts);
  12041. 12041   }
  12042. 12042  
  12043. 12043   // Workaround for bug, http://bugs.webkit.org/show_bug.cgi?id=12250
  12044. 12044   // WebKit can't even do simple things like selecting an image
  12045. 12045   // This also fixes so it's possible to select mceItemAnchors
  12046. 12046   if (tinymce.isWebKit) {
  12047. 12047   t.onClick.add(function(ed, e) {
  12048. 12048   e = e.target;
  12049. 12049  
  12050. 12050   // Needs tobe the setBaseAndExtend or it will fail to select floated images
  12051. 12051   if (e.nodeName == 'IMG' || (e.nodeName == 'A' && dom.hasClass(e, 'mceItemAnchor'))) {
  12052. 12052   t.selection.getSel().setBaseAndExtent(e, 0, e, 1);
  12053. 12053   t.nodeChanged();
  12054. 12054   }
  12055. 12055   });
  12056. 12056   }
  12057. 12057  
  12058. 12058   // Add node change handlers
  12059. 12059   t.onMouseUp.add(t.nodeChanged);
  12060. 12060   //t.onClick.add(t.nodeChanged);
  12061. 12061   t.onKeyUp.add(function(ed, e) {
  12062. 12062   var c = e.keyCode;
  12063. 12063  
  12064. 12064   if ((c >= 33 && c <= 36) || (c >= 37 && c <= 40) || c == 13 || c == 45 || c == 46 || c == 8 || (tinymce.isMac && (c == 91 || c == 93)) || e.ctrlKey)
  12065. 12065   t.nodeChanged();
  12066. 12066   });
  12067. 12067  
  12068. 12068   // Add reset handler
  12069. 12069   t.onReset.add(function() {
  12070. 12070   t.setContent(t.startContent, {format : 'raw'});
  12071. 12071   });
  12072. 12072  
  12073. 12073   // Add shortcuts
  12074. 12074   if (s.custom_shortcuts) {
  12075. 12075   if (s.custom_undo_redo_keyboard_shortcuts) {
  12076. 12076   t.addShortcut('ctrl+z', t.getLang('undo_desc'), 'Undo');
  12077. 12077   t.addShortcut('ctrl+y', t.getLang('redo_desc'), 'Redo');
  12078. 12078   }
  12079. 12079  
  12080. 12080   // Add default shortcuts for gecko
  12081. 12081   t.addShortcut('ctrl+b', t.getLang('bold_desc'), 'Bold');
  12082. 12082   t.addShortcut('ctrl+i', t.getLang('italic_desc'), 'Italic');
  12083. 12083   t.addShortcut('ctrl+u', t.getLang('underline_desc'), 'Underline');
  12084. 12084  
  12085. 12085   // BlockFormat shortcuts keys
  12086. 12086   for (i=1; i<=6; i++)
  12087. 12087   t.addShortcut('ctrl+' + i, '', ['FormatBlock', false, 'h' + i]);
  12088. 12088  
  12089. 12089   t.addShortcut('ctrl+7', '', ['FormatBlock', false, '<p>']);
  12090. 12090   t.addShortcut('ctrl+8', '', ['FormatBlock', false, '<div>']);
  12091. 12091   t.addShortcut('ctrl+9', '', ['FormatBlock', false, '<address>']);
  12092. 12092  
  12093. 12093   function find(e) {
  12094. 12094   var v = null;
  12095. 12095  
  12096. 12096   if (!e.altKey && !e.ctrlKey && !e.metaKey)
  12097. 12097   return v;
  12098. 12098  
  12099. 12099   each(t.shortcuts, function(o) {
  12100. 12100   if (tinymce.isMac && o.ctrl != e.metaKey)
  12101. 12101   return;
  12102. 12102   else if (!tinymce.isMac && o.ctrl != e.ctrlKey)
  12103. 12103   return;
  12104. 12104  
  12105. 12105   if (o.alt != e.altKey)
  12106. 12106   return;
  12107. 12107  
  12108. 12108   if (o.shift != e.shiftKey)
  12109. 12109   return;
  12110. 12110  
  12111. 12111   if (e.keyCode == o.keyCode || (e.charCode && e.charCode == o.charCode)) {
  12112. 12112   v = o;
  12113. 12113   return false;
  12114. 12114   }
  12115. 12115   });
  12116. 12116  
  12117. 12117   return v;
  12118. 12118   };
  12119. 12119  
  12120. 12120   t.onKeyUp.add(function(ed, e) {
  12121. 12121   var o = find(e);
  12122. 12122  
  12123. 12123   if (o)
  12124. 12124   return Event.cancel(e);
  12125. 12125   });
  12126. 12126  
  12127. 12127   t.onKeyPress.add(function(ed, e) {
  12128. 12128   var o = find(e);
  12129. 12129  
  12130. 12130   if (o)
  12131. 12131   return Event.cancel(e);
  12132. 12132   });
  12133. 12133  
  12134. 12134   t.onKeyDown.add(function(ed, e) {
  12135. 12135   var o = find(e);
  12136. 12136  
  12137. 12137   if (o) {
  12138. 12138   o.func.call(o.scope);
  12139. 12139   return Event.cancel(e);
  12140. 12140   }
  12141. 12141   });
  12142. 12142   }
  12143. 12143  
  12144. 12144   if (tinymce.isIE) {
  12145. 12145   // Fix so resize will only update the width and height attributes not the styles of an image
  12146. 12146   // It will also block mceItemNoResize items
  12147. 12147   dom.bind(t.getDoc(), 'controlselect', function(e) {
  12148. 12148   var re = t.resizeInfo, cb;
  12149. 12149  
  12150. 12150   e = e.target;
  12151. 12151  
  12152. 12152   // Don't do this action for non image elements
  12153. 12153   if (e.nodeName !== 'IMG')
  12154. 12154   return;
  12155. 12155  
  12156. 12156   if (re)
  12157. 12157   dom.unbind(re.node, re.ev, re.cb);
  12158. 12158  
  12159. 12159   if (!dom.hasClass(e, 'mceItemNoResize')) {
  12160. 12160   ev = 'resizeend';
  12161. 12161   cb = dom.bind(e, ev, function(e) {
  12162. 12162   var v;
  12163. 12163  
  12164. 12164   e = e.target;
  12165. 12165  
  12166. 12166   if (v = dom.getStyle(e, 'width')) {
  12167. 12167   dom.setAttrib(e, 'width', v.replace(/[^0-9%]+/g, ''));
  12168. 12168   dom.setStyle(e, 'width', '');
  12169. 12169   }
  12170. 12170  
  12171. 12171   if (v = dom.getStyle(e, 'height')) {
  12172. 12172   dom.setAttrib(e, 'height', v.replace(/[^0-9%]+/g, ''));
  12173. 12173   dom.setStyle(e, 'height', '');
  12174. 12174   }
  12175. 12175   });
  12176. 12176   } else {
  12177. 12177   ev = 'resizestart';
  12178. 12178   cb = dom.bind(e, 'resizestart', Event.cancel, Event);
  12179. 12179   }
  12180. 12180  
  12181. 12181   re = t.resizeInfo = {
  12182. 12182   node : e,
  12183. 12183   ev : ev,
  12184. 12184   cb : cb
  12185. 12185   };
  12186. 12186   });
  12187. 12187  
  12188. 12188   t.onKeyDown.add(function(ed, e) {
  12189. 12189   var sel;
  12190. 12190  
  12191. 12191   switch (e.keyCode) {
  12192. 12192   case 8:
  12193. 12193   sel = t.getDoc().selection;
  12194. 12194  
  12195. 12195   // Fix IE control + backspace browser bug
  12196. 12196   if (sel.createRange && sel.createRange().item) {
  12197. 12197   ed.dom.remove(sel.createRange().item(0));
  12198. 12198   return Event.cancel(e);
  12199. 12199   }
  12200. 12200   }
  12201. 12201   });
  12202. 12202   }
  12203. 12203  
  12204. 12204   if (tinymce.isOpera) {
  12205. 12205   t.onClick.add(function(ed, e) {
  12206. 12206   Event.prevent(e);
  12207. 12207   });
  12208. 12208   }
  12209. 12209  
  12210. 12210   // Add custom undo/redo handlers
  12211. 12211   if (s.custom_undo_redo) {
  12212. 12212   function addUndo() {
  12213. 12213   t.undoManager.typing = false;
  12214. 12214   t.undoManager.add();
  12215. 12215   };
  12216. 12216  
  12217. 12217   dom.bind(t.getDoc(), 'focusout', function(e) {
  12218. 12218   if (!t.removed && t.undoManager.typing)
  12219. 12219   addUndo();
  12220. 12220   });
  12221. 12221  
  12222. 12222   // Add undo level when contents is drag/dropped within the editor
  12223. 12223   t.dom.bind(t.dom.getRoot(), 'dragend', function(e) {
  12224. 12224   addUndo();
  12225. 12225   });
  12226. 12226  
  12227. 12227   t.onKeyUp.add(function(ed, e) {
  12228. 12228   var rng, parent, bookmark;
  12229. 12229  
  12230. 12230   // Fix for bug #3168, to remove odd ".." nodes from the DOM we need to get/set the HTML of the parent node.
  12231. 12231   if (isIE && e.keyCode == 8) {
  12232. 12232   rng = t.selection.getRng();
  12233. 12233   if (rng.parentElement) {
  12234. 12234   parent = rng.parentElement();
  12235. 12235   bookmark = t.selection.getBookmark();
  12236. 12236   parent.innerHTML = parent.innerHTML;
  12237. 12237   t.selection.moveToBookmark(bookmark);
  12238. 12238   }
  12239. 12239   }
  12240. 12240  
  12241. 12241   if ((e.keyCode >= 33 && e.keyCode <= 36) || (e.keyCode >= 37 && e.keyCode <= 40) || e.keyCode == 13 || e.keyCode == 45 || e.ctrlKey)
  12242. 12242   addUndo();
  12243. 12243   });
  12244. 12244  
  12245. 12245   t.onKeyDown.add(function(ed, e) {
  12246. 12246   var rng, parent, bookmark, keyCode = e.keyCode;
  12247. 12247  
  12248. 12248   // IE has a really odd bug where the DOM might include an node that doesn't have
  12249. 12249   // a proper structure. If you try to access nodeValue it would throw an illegal value exception.
  12250. 12250   // This seems to only happen when you delete contents and it seems to be avoidable if you refresh the element
  12251. 12251   // after you delete contents from it. See: #3008923
  12252. 12252   if (isIE && keyCode == 46) {
  12253. 12253   rng = t.selection.getRng();
  12254. 12254  
  12255. 12255   if (rng.parentElement) {
  12256. 12256   parent = rng.parentElement();
  12257. 12257  
  12258. 12258   if (!t.undoManager.typing) {
  12259. 12259   t.undoManager.beforeChange();
  12260. 12260   t.undoManager.typing = true;
  12261. 12261   t.undoManager.add();
  12262. 12262   }
  12263. 12263  
  12264. 12264   // Select next word when ctrl key is used in combo with delete
  12265. 12265   if (e.ctrlKey) {
  12266. 12266   rng.moveEnd('word', 1);
  12267. 12267   rng.select();
  12268. 12268   }
  12269. 12269  
  12270. 12270   // Delete contents
  12271. 12271   t.selection.getSel().clear();
  12272. 12272  
  12273. 12273   // Check if we are within the same parent
  12274. 12274   if (rng.parentElement() == parent) {
  12275. 12275   bookmark = t.selection.getBookmark();
  12276. 12276  
  12277. 12277   try {
  12278. 12278   // Update the HTML and hopefully it will remove the artifacts
  12279. 12279   parent.innerHTML = parent.innerHTML;
  12280. 12280   } catch (ex) {
  12281. 12281   // And since it's IE it can sometimes produce an unknown runtime error
  12282. 12282   }
  12283. 12283  
  12284. 12284   // Restore the caret position
  12285. 12285   t.selection.moveToBookmark(bookmark);
  12286. 12286   }
  12287. 12287  
  12288. 12288   // Block the default delete behavior since it might be broken
  12289. 12289   e.preventDefault();
  12290. 12290   return;
  12291. 12291   }
  12292. 12292   }
  12293. 12293  
  12294. 12294   // Is caracter positon keys left,right,up,down,home,end,pgdown,pgup,enter
  12295. 12295   if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 13 || keyCode == 45) {
  12296. 12296   // Add position before enter key is pressed, used by IE since it still uses the default browser behavior
  12297. 12297   // Todo: Remove this once we normalize enter behavior on IE
  12298. 12298   if (tinymce.isIE && keyCode == 13)
  12299. 12299   t.undoManager.beforeChange();
  12300. 12300  
  12301. 12301   if (t.undoManager.typing)
  12302. 12302   addUndo();
  12303. 12303  
  12304. 12304   return;
  12305. 12305   }
  12306. 12306  
  12307. 12307   // If key isn't shift,ctrl,alt,capslock,metakey
  12308. 12308   if ((keyCode < 16 || keyCode > 20) && keyCode != 224 && keyCode != 91 && !t.undoManager.typing) {
  12309. 12309   t.undoManager.beforeChange();
  12310. 12310   t.undoManager.add();
  12311. 12311   t.undoManager.typing = true;
  12312. 12312   }
  12313. 12313   });
  12314. 12314  
  12315. 12315   t.onMouseDown.add(function() {
  12316. 12316   if (t.undoManager.typing)
  12317. 12317   addUndo();
  12318. 12318   });
  12319. 12319   }
  12320. 12320  
  12321. 12321   // Bug fix for FireFox keeping styles from end of selection instead of start.
  12322. 12322   if (tinymce.isGecko) {
  12323. 12323   function getAttributeApplyFunction() {
  12324. 12324   var template = t.dom.getAttribs(t.selection.getStart().cloneNode(false));
  12325. 12325  
  12326. 12326   return function() {
  12327. 12327   var target = t.selection.getStart();
  12328. 12328   t.dom.removeAllAttribs(target);
  12329. 12329   each(template, function(attr) {
  12330. 12330   target.setAttributeNode(attr.cloneNode(true));
  12331. 12331   });
  12332. 12332   };
  12333. 12333   }
  12334. 12334  
  12335. 12335   function isSelectionAcrossElements() {
  12336. 12336   var s = t.selection;
  12337. 12337  
  12338. 12338   return !s.isCollapsed() && s.getStart() != s.getEnd();
  12339. 12339   }
  12340. 12340  
  12341. 12341   t.onKeyPress.add(function(ed, e) {
  12342. 12342   var applyAttributes;
  12343. 12343  
  12344. 12344   if ((e.keyCode == 8 || e.keyCode == 46) && isSelectionAcrossElements()) {
  12345. 12345   applyAttributes = getAttributeApplyFunction();
  12346. 12346   t.getDoc().execCommand('delete', false, null);
  12347. 12347   applyAttributes();
  12348. 12348  
  12349. 12349   return Event.cancel(e);
  12350. 12350   }
  12351. 12351   });
  12352. 12352  
  12353. 12353   t.dom.bind(t.getDoc(), 'cut', function(e) {
  12354. 12354   var applyAttributes;
  12355. 12355  
  12356. 12356   if (isSelectionAcrossElements()) {
  12357. 12357   applyAttributes = getAttributeApplyFunction();
  12358. 12358   t.onKeyUp.addToTop(Event.cancel, Event);
  12359. 12359  
  12360. 12360   setTimeout(function() {
  12361. 12361   applyAttributes();
  12362. 12362   t.onKeyUp.remove(Event.cancel, Event);
  12363. 12363   }, 0);
  12364. 12364   }
  12365. 12365   });
  12366. 12366   }
  12367. 12367   },
  12368. 12368  
  12369. 12369   _isHidden : function() {
  12370. 12370   var s;
  12371. 12371  
  12372. 12372   if (!isGecko)
  12373. 12373   return 0;
  12374. 12374  
  12375. 12375   // Weird, wheres that cursor selection?
  12376. 12376   s = this.selection.getSel();
  12377. 12377   return (!s || !s.rangeCount || s.rangeCount == 0);
  12378. 12378   }
  12379. 12379   });
  12380. 12380  })(tinymce);
  12381. 12381  
  12382. 12382  (function(tinymce) {
  12383. 12383   // Added for compression purposes
  12384. 12384   var each = tinymce.each, undefined, TRUE = true, FALSE = false;
  12385. 12385  
  12386. 12386   tinymce.EditorCommands = function(editor) {
  12387. 12387   var dom = editor.dom,
  12388. 12388   selection = editor.selection,
  12389. 12389   commands = {state: {}, exec : {}, value : {}},
  12390. 12390   settings = editor.settings,
  12391. 12391   bookmark;
  12392. 12392  
  12393. 12393   function execCommand(command, ui, value) {
  12394. 12394   var func;
  12395. 12395  
  12396. 12396   command = command.toLowerCase();
  12397. 12397   if (func = commands.exec[command]) {
  12398. 12398   func(command, ui, value);
  12399. 12399   return TRUE;
  12400. 12400   }
  12401. 12401  
  12402. 12402   return FALSE;
  12403. 12403   };
  12404. 12404  
  12405. 12405   function queryCommandState(command) {
  12406. 12406   var func;
  12407. 12407  
  12408. 12408   command = command.toLowerCase();
  12409. 12409   if (func = commands.state[command])
  12410. 12410   return func(command);
  12411. 12411  
  12412. 12412   return -1;
  12413. 12413   };
  12414. 12414  
  12415. 12415   function queryCommandValue(command) {
  12416. 12416   var func;
  12417. 12417  
  12418. 12418   command = command.toLowerCase();
  12419. 12419   if (func = commands.value[command])
  12420. 12420   return func(command);
  12421. 12421  
  12422. 12422   return FALSE;
  12423. 12423   };
  12424. 12424  
  12425. 12425   function addCommands(command_list, type) {
  12426. 12426   type = type || 'exec';
  12427. 12427  
  12428. 12428   each(command_list, function(callback, command) {
  12429. 12429   each(command.toLowerCase().split(','), function(command) {
  12430. 12430   commands[type][command] = callback;
  12431. 12431   });
  12432. 12432   });
  12433. 12433   };
  12434. 12434  
  12435. 12435   // Expose public methods
  12436. 12436   tinymce.extend(this, {
  12437. 12437   execCommand : execCommand,
  12438. 12438   queryCommandState : queryCommandState,
  12439. 12439   queryCommandValue : queryCommandValue,
  12440. 12440   addCommands : addCommands
  12441. 12441   });
  12442. 12442  
  12443. 12443   // Private methods
  12444. 12444  
  12445. 12445   function execNativeCommand(command, ui, value) {
  12446. 12446   if (ui === undefined)
  12447. 12447   ui = FALSE;
  12448. 12448  
  12449. 12449   if (value === undefined)
  12450. 12450   value = null;
  12451. 12451  
  12452. 12452   return editor.getDoc().execCommand(command, ui, value);
  12453. 12453   };
  12454. 12454  
  12455. 12455   function isFormatMatch(name) {
  12456. 12456   return editor.formatter.match(name);
  12457. 12457   };
  12458. 12458  
  12459. 12459   function toggleFormat(name, value) {
  12460. 12460   editor.formatter.toggle(name, value ? {value : value} : undefined);
  12461. 12461   };
  12462. 12462  
  12463. 12463   function storeSelection(type) {
  12464. 12464   bookmark = selection.getBookmark(type);
  12465. 12465   };
  12466. 12466  
  12467. 12467   function restoreSelection() {
  12468. 12468   selection.moveToBookmark(bookmark);
  12469. 12469   };
  12470. 12470  
  12471. 12471   // Add execCommand overrides
  12472. 12472   addCommands({
  12473. 12473   // Ignore these, added for compatibility
  12474. 12474   'mceResetDesignMode,mceBeginUndoLevel' : function() {},
  12475. 12475  
  12476. 12476   // Add undo manager logic
  12477. 12477   'mceEndUndoLevel,mceAddUndoLevel' : function() {
  12478. 12478   editor.undoManager.add();
  12479. 12479   },
  12480. 12480  
  12481. 12481   'Cut,Copy,Paste' : function(command) {
  12482. 12482   var doc = editor.getDoc(), failed;
  12483. 12483  
  12484. 12484   // Try executing the native command
  12485. 12485   try {
  12486. 12486   execNativeCommand(command);
  12487. 12487   } catch (ex) {
  12488. 12488   // Command failed
  12489. 12489   failed = TRUE;
  12490. 12490   }
  12491. 12491  
  12492. 12492   // Present alert message about clipboard access not being available
  12493. 12493   if (failed || !doc.queryCommandSupported(command)) {
  12494. 12494   if (tinymce.isGecko) {
  12495. 12495   editor.windowManager.confirm(editor.getLang('clipboard_msg'), function(state) {
  12496. 12496   if (state)
  12497. 12497   open('http://www.mozilla.org/editor/midasdemo/securityprefs.html', '_blank');
  12498. 12498   });
  12499. 12499   } else
  12500. 12500   editor.windowManager.alert(editor.getLang('clipboard_no_support'));
  12501. 12501   }
  12502. 12502   },
  12503. 12503  
  12504. 12504   // Override unlink command
  12505. 12505   unlink : function(command) {
  12506. 12506   if (selection.isCollapsed())
  12507. 12507   selection.select(selection.getNode());
  12508. 12508  
  12509. 12509   execNativeCommand(command);
  12510. 12510   selection.collapse(FALSE);
  12511. 12511   },
  12512. 12512  
  12513. 12513   // Override justify commands to use the text formatter engine
  12514. 12514   'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) {
  12515. 12515   var align = command.substring(7);
  12516. 12516  
  12517. 12517   // Remove all other alignments first
  12518. 12518   each('left,center,right,full'.split(','), function(name) {
  12519. 12519   if (align != name)
  12520. 12520   editor.formatter.remove('align' + name);
  12521. 12521   });
  12522. 12522  
  12523. 12523   toggleFormat('align' + align);
  12524. 12524   execCommand('mceRepaint');
  12525. 12525   },
  12526. 12526  
  12527. 12527   // Override list commands to fix WebKit bug
  12528. 12528   'InsertUnorderedList,InsertOrderedList' : function(command) {
  12529. 12529   var listElm, listParent;
  12530. 12530  
  12531. 12531   execNativeCommand(command);
  12532. 12532  
  12533. 12533   // WebKit produces lists within block elements so we need to split them
  12534. 12534   // we will replace the native list creation logic to custom logic later on
  12535. 12535   // TODO: Remove this when the list creation logic is removed
  12536. 12536   listElm = dom.getParent(selection.getNode(), 'ol,ul');
  12537. 12537   if (listElm) {
  12538. 12538   listParent = listElm.parentNode;
  12539. 12539  
  12540. 12540   // If list is within a text block then split that block
  12541. 12541   if (/^(H[1-6]|P|ADDRESS|PRE)$/.test(listParent.nodeName)) {
  12542. 12542   storeSelection();
  12543. 12543   dom.split(listParent, listElm);
  12544. 12544   restoreSelection();
  12545. 12545   }
  12546. 12546   }
  12547. 12547   },
  12548. 12548  
  12549. 12549   // Override commands to use the text formatter engine
  12550. 12550   'Bold,Italic,Underline,Strikethrough,Superscript,Subscript' : function(command) {
  12551. 12551   toggleFormat(command);
  12552. 12552   },
  12553. 12553  
  12554. 12554   // Override commands to use the text formatter engine
  12555. 12555   'ForeColor,HiliteColor,FontName' : function(command, ui, value) {
  12556. 12556   toggleFormat(command, value);
  12557. 12557   },
  12558. 12558  
  12559. 12559   FontSize : function(command, ui, value) {
  12560. 12560   var fontClasses, fontSizes;
  12561. 12561  
  12562. 12562   // Convert font size 1-7 to styles
  12563. 12563   if (value >= 1 && value <= 7) {
  12564. 12564   fontSizes = tinymce.explode(settings.font_size_style_values);
  12565. 12565   fontClasses = tinymce.explode(settings.font_size_classes);
  12566. 12566  
  12567. 12567   if (fontClasses)
  12568. 12568   value = fontClasses[value - 1] || value;
  12569. 12569   else
  12570. 12570   value = fontSizes[value - 1] || value;
  12571. 12571   }
  12572. 12572  
  12573. 12573   toggleFormat(command, value);
  12574. 12574   },
  12575. 12575  
  12576. 12576   RemoveFormat : function(command) {
  12577. 12577   editor.formatter.remove(command);
  12578. 12578   },
  12579. 12579  
  12580. 12580   mceBlockQuote : function(command) {
  12581. 12581   toggleFormat('blockquote');
  12582. 12582   },
  12583. 12583  
  12584. 12584   FormatBlock : function(command, ui, value) {
  12585. 12585   return toggleFormat(value || 'p');
  12586. 12586   },
  12587. 12587  
  12588. 12588   mceCleanup : function() {
  12589. 12589   var bookmark = selection.getBookmark();
  12590. 12590  
  12591. 12591   editor.setContent(editor.getContent({cleanup : TRUE}), {cleanup : TRUE});
  12592. 12592  
  12593. 12593   selection.moveToBookmark(bookmark);
  12594. 12594   },
  12595. 12595  
  12596. 12596   mceRemoveNode : function(command, ui, value) {
  12597. 12597   var node = value || selection.getNode();
  12598. 12598  
  12599. 12599   // Make sure that the body node isn't removed
  12600. 12600   if (node != editor.getBody()) {
  12601. 12601   storeSelection();
  12602. 12602   editor.dom.remove(node, TRUE);
  12603. 12603   restoreSelection();
  12604. 12604   }
  12605. 12605   },
  12606. 12606  
  12607. 12607   mceSelectNodeDepth : function(command, ui, value) {
  12608. 12608   var counter = 0;
  12609. 12609  
  12610. 12610   dom.getParent(selection.getNode(), function(node) {
  12611. 12611   if (node.nodeType == 1 && counter++ == value) {
  12612. 12612   selection.select(node);
  12613. 12613   return FALSE;
  12614. 12614   }
  12615. 12615   }, editor.getBody());
  12616. 12616   },
  12617. 12617  
  12618. 12618   mceSelectNode : function(command, ui, value) {
  12619. 12619   selection.select(value);
  12620. 12620   },
  12621. 12621  
  12622. 12622   mceInsertContent : function(command, ui, value) {
  12623. 12623   var caretNode, rng, rootNode, parent, node, rng, nodeRect, viewPortRect, args;
  12624. 12624  
  12625. 12625   function findSuitableCaretNode(start_node, root_node) {
  12626. 12626   var node, walker = new tinymce.dom.TreeWalker(start_node, root_node);
  12627. 12627  
  12628. 12628   while ((node = walker.current())) {
  12629. 12629   if ((node.nodeType == 3 && tinymce.trim(node.nodeValue).length) || node.nodeName == 'BR' || node.nodeName == 'IMG')
  12630. 12630   return node;
  12631. 12631  
  12632. 12632   walker.prev();
  12633. 12633   }
  12634. 12634   };
  12635. 12635  
  12636. 12636   args = {content: value, format: 'html'};
  12637. 12637   selection.onBeforeSetContent.dispatch(selection, args);
  12638. 12638   value = args.content;
  12639. 12639  
  12640. 12640   // Add caret at end of contents if it's missing
  12641. 12641   if (value.indexOf('{$caret}') == -1)
  12642. 12642   value += '{$caret}';
  12643. 12643  
  12644. 12644   // Set the content at selection to a span and replace it's contents with the value
  12645. 12645   selection.setContent('<span id="__mce">\uFEFF</span>', {no_events : false});
  12646. 12646   dom.setOuterHTML('__mce', value.replace(/\{\$caret\}/, '<span data-mce-type="bookmark" id="__mce">\uFEFF</span>'));
  12647. 12647  
  12648. 12648   caretNode = dom.select('#__mce')[0];
  12649. 12649   rootNode = dom.getRoot();
  12650. 12650  
  12651. 12651   // Move the caret into the last suitable location within the previous sibling if it's a block since the block might be split
  12652. 12652   if (caretNode.previousSibling && dom.isBlock(caretNode.previousSibling) || caretNode.parentNode == rootNode) {
  12653. 12653   node = findSuitableCaretNode(caretNode.previousSibling, rootNode);
  12654. 12654   if (node) {
  12655. 12655   if (node.nodeName == 'BR')
  12656. 12656   node.parentNode.insertBefore(caretNode, node);
  12657. 12657   else
  12658. 12658   dom.insertAfter(caretNode, node);
  12659. 12659   }
  12660. 12660   }
  12661. 12661  
  12662. 12662   // Find caret root parent and clean it up using the serializer to avoid nesting
  12663. 12663   while (caretNode) {
  12664. 12664   if (caretNode === rootNode) {
  12665. 12665   // Clean up the parent element by parsing and serializing it
  12666. 12666   // This will remove invalid elements/attributes and fix nesting issues
  12667. 12667   dom.setOuterHTML(parent,
  12668. 12668   new tinymce.html.Serializer({}, editor.schema).serialize(
  12669. 12669   new tinymce.html.DomParser({
  12670. 12670   remove_trailing_brs : true
  12671. 12671   }, editor.schema).parse(dom.getOuterHTML(parent))
  12672. 12672   )
  12673. 12673   );
  12674. 12674  
  12675. 12675   break;
  12676. 12676   }
  12677. 12677  
  12678. 12678   parent = caretNode;
  12679. 12679   caretNode = caretNode.parentNode;
  12680. 12680   }
  12681. 12681  
  12682. 12682   // Find caret after cleanup and move selection to that location
  12683. 12683   caretNode = dom.select('#__mce')[0];
  12684. 12684   if (caretNode) {
  12685. 12685   node = findSuitableCaretNode(caretNode.previousSibling, rootNode);
  12686. 12686   dom.remove(caretNode);
  12687. 12687  
  12688. 12688   if (node) {
  12689. 12689   rng = dom.createRng();
  12690. 12690  
  12691. 12691   if (node.nodeType == 3) {
  12692. 12692   rng.setStart(node, node.length);
  12693. 12693   rng.setEnd(node, node.length);
  12694. 12694   } else {
  12695. 12695   if (node.nodeName == 'BR') {
  12696. 12696   rng.setStartBefore(node);
  12697. 12697   rng.setEndBefore(node);
  12698. 12698   } else {
  12699. 12699   rng.setStartAfter(node);
  12700. 12700   rng.setEndAfter(node);
  12701. 12701   }
  12702. 12702   }
  12703. 12703  
  12704. 12704   selection.setRng(rng);
  12705. 12705  
  12706. 12706   // Scroll range into view scrollIntoView on element can't be used since it will scroll the main view port as well
  12707. 12707   if (!tinymce.isIE) {
  12708. 12708   node = dom.create('span', null, '\u00a0');
  12709. 12709   rng.insertNode(node);
  12710. 12710   nodeRect = dom.getRect(node);
  12711. 12711   viewPortRect = dom.getViewPort(editor.getWin());
  12712. 12712  
  12713. 12713   // Check if node is out side the viewport if it is then scroll to it
  12714. 12714   if ((nodeRect.y > viewPortRect.y + viewPortRect.h || nodeRect.y < viewPortRect.y) ||
  12715. 12715   (nodeRect.x > viewPortRect.x + viewPortRect.w || nodeRect.x < viewPortRect.x)) {
  12716. 12716   editor.getBody().scrollLeft = nodeRect.x;
  12717. 12717   editor.getBody().scrollTop = nodeRect.y;
  12718. 12718   }
  12719. 12719  
  12720. 12720   dom.remove(node);
  12721. 12721   }
  12722. 12722  
  12723. 12723   // Make sure that the selection is collapsed after we removed the node fixes a WebKit bug
  12724. 12724   // where WebKit would place the endContainer/endOffset at a different location than the startContainer/startOffset
  12725. 12725   selection.collapse(true);
  12726. 12726   }
  12727. 12727   }
  12728. 12728  
  12729. 12729   selection.onSetContent.dispatch(selection, args);
  12730. 12730   editor.addVisual();
  12731. 12731   },
  12732. 12732  
  12733. 12733   mceInsertRawHTML : function(command, ui, value) {
  12734. 12734   selection.setContent('tiny_mce_marker');
  12735. 12735   editor.setContent(editor.getContent().replace(/tiny_mce_marker/g, function() { return value }));
  12736. 12736   },
  12737. 12737  
  12738. 12738   mceSetContent : function(command, ui, value) {
  12739. 12739   editor.setContent(value);
  12740. 12740   },
  12741. 12741  
  12742. 12742   'Indent,Outdent' : function(command) {
  12743. 12743   var intentValue, indentUnit, value;
  12744. 12744  
  12745. 12745   // Setup indent level
  12746. 12746   intentValue = settings.indentation;
  12747. 12747   indentUnit = /[a-z%]+$/i.exec(intentValue);
  12748. 12748   intentValue = parseInt(intentValue);
  12749. 12749  
  12750. 12750   if (!queryCommandState('InsertUnorderedList') && !queryCommandState('InsertOrderedList')) {
  12751. 12751   each(selection.getSelectedBlocks(), function(element) {
  12752. 12752   if (command == 'outdent') {
  12753. 12753   value = Math.max(0, parseInt(element.style.paddingLeft || 0) - intentValue);
  12754. 12754   dom.setStyle(element, 'paddingLeft', value ? value + indentUnit : '');
  12755. 12755   } else
  12756. 12756   dom.setStyle(element, 'paddingLeft', (parseInt(element.style.paddingLeft || 0) + intentValue) + indentUnit);
  12757. 12757   });
  12758. 12758   } else
  12759. 12759   execNativeCommand(command);
  12760. 12760   },
  12761. 12761  
  12762. 12762   mceRepaint : function() {
  12763. 12763   var bookmark;
  12764. 12764  
  12765. 12765   if (tinymce.isGecko) {
  12766. 12766   try {
  12767. 12767   storeSelection(TRUE);
  12768. 12768  
  12769. 12769   if (selection.getSel())
  12770. 12770   selection.getSel().selectAllChildren(editor.getBody());
  12771. 12771  
  12772. 12772   selection.collapse(TRUE);
  12773. 12773   restoreSelection();
  12774. 12774   } catch (ex) {
  12775. 12775   // Ignore
  12776. 12776   }
  12777. 12777   }
  12778. 12778   },
  12779. 12779  
  12780. 12780   mceToggleFormat : function(command, ui, value) {
  12781. 12781   editor.formatter.toggle(value);
  12782. 12782   },
  12783. 12783  
  12784. 12784   InsertHorizontalRule : function() {
  12785. 12785   editor.execCommand('mceInsertContent', false, '<hr />');
  12786. 12786   },
  12787. 12787  
  12788. 12788   mceToggleVisualAid : function() {
  12789. 12789   editor.hasVisual = !editor.hasVisual;
  12790. 12790   editor.addVisual();
  12791. 12791   },
  12792. 12792  
  12793. 12793   mceReplaceContent : function(command, ui, value) {
  12794. 12794   editor.execCommand('mceInsertContent', false, selection.setContent(value.replace(/\{\$selection\}/g, selection.getContent({format : 'text'}))));
  12795. 12795   },
  12796. 12796  
  12797. 12797   mceInsertLink : function(command, ui, value) {
  12798. 12798   var link = dom.getParent(selection.getNode(), 'a'), img, floatVal;
  12799. 12799  
  12800. 12800   if (tinymce.is(value, 'string'))
  12801. 12801   value = {href : value};
  12802. 12802  
  12803. 12803   // Spaces are never valid in URLs and it's a very common mistake for people to make so we fix it here.
  12804. 12804   value.href = value.href.replace(' ', '%20');
  12805. 12805  
  12806. 12806   if (!link) {
  12807. 12807   // WebKit can't create links on float images for some odd reason so just remove it and restore it later
  12808. 12808   if (tinymce.isWebKit) {
  12809. 12809   img = dom.getParent(selection.getNode(), 'img');
  12810. 12810  
  12811. 12811   if (img) {
  12812. 12812   floatVal = img.style.cssFloat;
  12813. 12813   img.style.cssFloat = null;
  12814. 12814   }
  12815. 12815   }
  12816. 12816  
  12817. 12817   execNativeCommand('CreateLink', FALSE, 'javascript:mctmp(0);');
  12818. 12818  
  12819. 12819   // Restore float value
  12820. 12820   if (floatVal)
  12821. 12821   img.style.cssFloat = floatVal;
  12822. 12822  
  12823. 12823   each(dom.select("a[href='javascript:mctmp(0);']"), function(link) {
  12824. 12824   dom.setAttribs(link, value);
  12825. 12825   });
  12826. 12826   } else {
  12827. 12827   if (value.href)
  12828. 12828   dom.setAttribs(link, value);
  12829. 12829   else
  12830. 12830   editor.dom.remove(link, TRUE);
  12831. 12831   }
  12832. 12832   },
  12833. 12833  
  12834. 12834   selectAll : function() {
  12835. 12835   var root = dom.getRoot(), rng = dom.createRng();
  12836. 12836  
  12837. 12837   rng.setStart(root, 0);
  12838. 12838   rng.setEnd(root, root.childNodes.length);
  12839. 12839  
  12840. 12840   editor.selection.setRng(rng);
  12841. 12841   }
  12842. 12842   });
  12843. 12843  
  12844. 12844   // Add queryCommandState overrides
  12845. 12845   addCommands({
  12846. 12846   // Override justify commands
  12847. 12847   'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) {
  12848. 12848   return isFormatMatch('align' + command.substring(7));
  12849. 12849   },
  12850. 12850  
  12851. 12851   'Bold,Italic,Underline,Strikethrough,Superscript,Subscript' : function(command) {
  12852. 12852   return isFormatMatch(command);
  12853. 12853   },
  12854. 12854  
  12855. 12855   mceBlockQuote : function() {
  12856. 12856   return isFormatMatch('blockquote');
  12857. 12857   },
  12858. 12858  
  12859. 12859   Outdent : function() {
  12860. 12860   var node;
  12861. 12861  
  12862. 12862   if (settings.inline_styles) {
  12863. 12863   if ((node = dom.getParent(selection.getStart(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0)
  12864. 12864   return TRUE;
  12865. 12865  
  12866. 12866   if ((node = dom.getParent(selection.getEnd(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0)
  12867. 12867   return TRUE;
  12868. 12868   }
  12869. 12869  
  12870. 12870   return queryCommandState('InsertUnorderedList') || queryCommandState('InsertOrderedList') || (!settings.inline_styles && !!dom.getParent(selection.getNode(), 'BLOCKQUOTE'));
  12871. 12871   },
  12872. 12872  
  12873. 12873   'InsertUnorderedList,InsertOrderedList' : function(command) {
  12874. 12874   return dom.getParent(selection.getNode(), command == 'insertunorderedlist' ? 'UL' : 'OL');
  12875. 12875   }
  12876. 12876   }, 'state');
  12877. 12877  
  12878. 12878   // Add queryCommandValue overrides
  12879. 12879   addCommands({
  12880. 12880   'FontSize,FontName' : function(command) {
  12881. 12881   var value = 0, parent;
  12882. 12882  
  12883. 12883   if (parent = dom.getParent(selection.getNode(), 'span')) {
  12884. 12884   if (command == 'fontsize')
  12885. 12885   value = parent.style.fontSize;
  12886. 12886   else
  12887. 12887   value = parent.style.fontFamily.replace(/, /g, ',').replace(/[\'\"]/g, '').toLowerCase();
  12888. 12888   }
  12889. 12889  
  12890. 12890   return value;
  12891. 12891   }
  12892. 12892   }, 'value');
  12893. 12893  
  12894. 12894   // Add undo manager logic
  12895. 12895   if (settings.custom_undo_redo) {
  12896. 12896   addCommands({
  12897. 12897   Undo : function() {
  12898. 12898   editor.undoManager.undo();
  12899. 12899   },
  12900. 12900  
  12901. 12901   Redo : function() {
  12902. 12902   editor.undoManager.redo();
  12903. 12903   }
  12904. 12904   });
  12905. 12905   }
  12906. 12906   };
  12907. 12907  })(tinymce);
  12908. 12908  
  12909. 12909  (function(tinymce) {
  12910. 12910   var Dispatcher = tinymce.util.Dispatcher;
  12911. 12911  
  12912. 12912   tinymce.UndoManager = function(editor) {
  12913. 12913   var self, index = 0, data = [];
  12914. 12914  
  12915. 12915   function getContent() {
  12916. 12916   return tinymce.trim(editor.getContent({format : 'raw', no_events : 1}));
  12917. 12917   };
  12918. 12918  
  12919. 12919   return self = {
  12920. 12920   typing : false,
  12921. 12921  
  12922. 12922   onAdd : new Dispatcher(self),
  12923. 12923  
  12924. 12924   onUndo : new Dispatcher(self),
  12925. 12925  
  12926. 12926   onRedo : new Dispatcher(self),
  12927. 12927  
  12928. 12928   beforeChange : function() {
  12929. 12929   // Set before bookmark on previous level
  12930. 12930   if (data[index])
  12931. 12931   data[index].beforeBookmark = editor.selection.getBookmark(2, true);
  12932. 12932   },
  12933. 12933  
  12934. 12934   add : function(level) {
  12935. 12935   var i, settings = editor.settings, lastLevel;
  12936. 12936  
  12937. 12937   level = level || {};
  12938. 12938   level.content = getContent();
  12939. 12939  
  12940. 12940   // Add undo level if needed
  12941. 12941   lastLevel = data[index];
  12942. 12942   if (lastLevel && lastLevel.content == level.content)
  12943. 12943   return null;
  12944. 12944  
  12945. 12945   // Time to compress
  12946. 12946   if (settings.custom_undo_redo_levels) {
  12947. 12947   if (data.length > settings.custom_undo_redo_levels) {
  12948. 12948   for (i = 0; i < data.length - 1; i++)
  12949. 12949   data[i] = data[i + 1];
  12950. 12950  
  12951. 12951   data.length--;
  12952. 12952   index = data.length;
  12953. 12953   }
  12954. 12954   }
  12955. 12955  
  12956. 12956   // Get a non intrusive normalized bookmark
  12957. 12957   level.bookmark = editor.selection.getBookmark(2, true);
  12958. 12958  
  12959. 12959   // Crop array if needed
  12960. 12960   if (index < data.length - 1)
  12961. 12961   data.length = index + 1;
  12962. 12962  
  12963. 12963   data.push(level);
  12964. 12964   index = data.length - 1;
  12965. 12965  
  12966. 12966   self.onAdd.dispatch(self, level);
  12967. 12967   editor.isNotDirty = 0;
  12968. 12968  
  12969. 12969   return level;
  12970. 12970   },
  12971. 12971  
  12972. 12972   undo : function() {
  12973. 12973   var level, i;
  12974. 12974  
  12975. 12975   if (self.typing) {
  12976. 12976   self.add();
  12977. 12977   self.typing = false;
  12978. 12978   }
  12979. 12979  
  12980. 12980   if (index > 0) {
  12981. 12981   level = data[--index];
  12982. 12982  
  12983. 12983   editor.setContent(level.content, {format : 'raw'});
  12984. 12984   editor.selection.moveToBookmark(level.beforeBookmark);
  12985. 12985  
  12986. 12986   self.onUndo.dispatch(self, level);
  12987. 12987   }
  12988. 12988  
  12989. 12989   return level;
  12990. 12990   },
  12991. 12991  
  12992. 12992   redo : function() {
  12993. 12993   var level;
  12994. 12994  
  12995. 12995   if (index < data.length - 1) {
  12996. 12996   level = data[++index];
  12997. 12997  
  12998. 12998   editor.setContent(level.content, {format : 'raw'});
  12999. 12999   editor.selection.moveToBookmark(level.bookmark);
  13000. 13000  
  13001. 13001   self.onRedo.dispatch(self, level);
  13002. 13002   }
  13003. 13003  
  13004. 13004   return level;
  13005. 13005   },
  13006. 13006  
  13007. 13007   clear : function() {
  13008. 13008   data = [];
  13009. 13009   index = 0;
  13010. 13010   self.typing = false;
  13011. 13011   },
  13012. 13012  
  13013. 13013   hasUndo : function() {
  13014. 13014   return index > 0 || this.typing;
  13015. 13015   },
  13016. 13016  
  13017. 13017   hasRedo : function() {
  13018. 13018   return index < data.length - 1 && !this.typing;
  13019. 13019   }
  13020. 13020   };
  13021. 13021   };
  13022. 13022  })(tinymce);
  13023. 13023  
  13024. 13024  (function(tinymce) {
  13025. 13025   // Shorten names
  13026. 13026   var Event = tinymce.dom.Event,
  13027. 13027   isIE = tinymce.isIE,
  13028. 13028   isGecko = tinymce.isGecko,
  13029. 13029   isOpera = tinymce.isOpera,
  13030. 13030   each = tinymce.each,
  13031. 13031   extend = tinymce.extend,
  13032. 13032   TRUE = true,
  13033. 13033   FALSE = false;
  13034. 13034  
  13035. 13035   function cloneFormats(node) {
  13036. 13036   var clone, temp, inner;
  13037. 13037  
  13038. 13038   do {
  13039. 13039   if (/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U)$/.test(node.nodeName)) {
  13040. 13040   if (clone) {
  13041. 13041   temp = node.cloneNode(false);
  13042. 13042   temp.appendChild(clone);
  13043. 13043   clone = temp;
  13044. 13044   } else {
  13045. 13045   clone = inner = node.cloneNode(false);
  13046. 13046   }
  13047. 13047  
  13048. 13048   clone.removeAttribute('id');
  13049. 13049   }
  13050. 13050   } while (node = node.parentNode);
  13051. 13051  
  13052. 13052   if (clone)
  13053. 13053   return {wrapper : clone, inner : inner};
  13054. 13054   };
  13055. 13055  
  13056. 13056   // Checks if the selection/caret is at the end of the specified block element
  13057. 13057   function isAtEnd(rng, par) {
  13058. 13058   var rng2 = par.ownerDocument.createRange();
  13059. 13059  
  13060. 13060   rng2.setStart(rng.endContainer, rng.endOffset);
  13061. 13061   rng2.setEndAfter(par);
  13062. 13062  
  13063. 13063   // Get number of characters to the right of the cursor if it's zero then we are at the end and need to merge the next block element
  13064. 13064   return rng2.cloneContents().textContent.length == 0;
  13065. 13065   };
  13066. 13066  
  13067. 13067   function isEmpty(n) {
  13068. 13068   n = n.innerHTML;
  13069. 13069  
  13070. 13070   n = n.replace(/<(img|hr|table|input|select|textarea)[ \>]/gi, '-'); // Keep these convert them to - chars
  13071. 13071   n = n.replace(/<[^>]+>/g, ''); // Remove all tags
  13072. 13072  
  13073. 13073   return n.replace(/[ \u00a0\t\r\n]+/g, '') == '';
  13074. 13074   };
  13075. 13075  
  13076. 13076   function splitList(selection, dom, li) {
  13077. 13077   var listBlock, block;
  13078. 13078  
  13079. 13079   if (isEmpty(li)) {
  13080. 13080   listBlock = dom.getParent(li, 'ul,ol');
  13081. 13081  
  13082. 13082   if (!dom.getParent(listBlock.parentNode, 'ul,ol')) {
  13083. 13083   dom.split(listBlock, li);
  13084. 13084   block = dom.create('p', 0, '<br data-mce-bogus="1" />');
  13085. 13085   dom.replace(block, li);
  13086. 13086   selection.select(block, 1);
  13087. 13087   }
  13088. 13088  
  13089. 13089   return FALSE;
  13090. 13090   }
  13091. 13091  
  13092. 13092   return TRUE;
  13093. 13093   };
  13094. 13094  
  13095. 13095   tinymce.create('tinymce.ForceBlocks', {
  13096. 13096   ForceBlocks : function(ed) {
  13097. 13097   var t = this, s = ed.settings, elm;
  13098. 13098  
  13099. 13099   t.editor = ed;
  13100. 13100   t.dom = ed.dom;
  13101. 13101   elm = (s.forced_root_block || 'p').toLowerCase();
  13102. 13102   s.element = elm.toUpperCase();
  13103. 13103  
  13104. 13104   ed.onPreInit.add(t.setup, t);
  13105. 13105  
  13106. 13106   if (s.forced_root_block) {
  13107. 13107   ed.onInit.add(t.forceRoots, t);
  13108. 13108   ed.onSetContent.add(t.forceRoots, t);
  13109. 13109   ed.onBeforeGetContent.add(t.forceRoots, t);
  13110. 13110   ed.onExecCommand.add(function(ed, cmd) {
  13111. 13111   if (cmd == 'mceInsertContent') {
  13112. 13112   t.forceRoots();
  13113. 13113   ed.nodeChanged();
  13114. 13114   }
  13115. 13115   });
  13116. 13116   }
  13117. 13117   },
  13118. 13118  
  13119. 13119   setup : function() {
  13120. 13120   var t = this, ed = t.editor, s = ed.settings, dom = ed.dom, selection = ed.selection;
  13121. 13121  
  13122. 13122   // Force root blocks when typing and when getting output
  13123. 13123   if (s.forced_root_block) {
  13124. 13124   ed.onBeforeExecCommand.add(t.forceRoots, t);
  13125. 13125   ed.onKeyUp.add(t.forceRoots, t);
  13126. 13126   ed.onPreProcess.add(t.forceRoots, t);
  13127. 13127   }
  13128. 13128  
  13129. 13129   if (s.force_br_newlines) {
  13130. 13130   // Force IE to produce BRs on enter
  13131. 13131   if (isIE) {
  13132. 13132   ed.onKeyPress.add(function(ed, e) {
  13133. 13133   var n;
  13134. 13134  
  13135. 13135   if (e.keyCode == 13 && selection.getNode().nodeName != 'LI') {
  13136. 13136   selection.setContent('<br id="__" /> ', {format : 'raw'});
  13137. 13137   n = dom.get('__');
  13138. 13138   n.removeAttribute('id');
  13139. 13139   selection.select(n);
  13140. 13140   selection.collapse();
  13141. 13141   return Event.cancel(e);
  13142. 13142   }
  13143. 13143   });
  13144. 13144   }
  13145. 13145   }
  13146. 13146  
  13147. 13147   if (s.force_p_newlines) {
  13148. 13148   if (!isIE) {
  13149. 13149   ed.onKeyPress.add(function(ed, e) {
  13150. 13150   if (e.keyCode == 13 && !e.shiftKey && !t.insertPara(e))
  13151. 13151   Event.cancel(e);
  13152. 13152   });
  13153. 13153   } else {
  13154. 13154   // Ungly hack to for IE to preserve the formatting when you press
  13155. 13155   // enter at the end of a block element with formatted contents
  13156. 13156   // This logic overrides the browsers default logic with
  13157. 13157   // custom logic that enables us to control the output
  13158. 13158   tinymce.addUnload(function() {
  13159. 13159   t._previousFormats = 0; // Fix IE leak
  13160. 13160   });
  13161. 13161  
  13162. 13162   ed.onKeyPress.add(function(ed, e) {
  13163. 13163   t._previousFormats = 0;
  13164. 13164  
  13165. 13165   // Clone the current formats, this will later be applied to the new block contents
  13166. 13166   if (e.keyCode == 13 && !e.shiftKey && ed.selection.isCollapsed() && s.keep_styles)
  13167. 13167   t._previousFormats = cloneFormats(ed.selection.getStart());
  13168. 13168   });
  13169. 13169  
  13170. 13170   ed.onKeyUp.add(function(ed, e) {
  13171. 13171   // Let IE break the element and the wrap the new caret location in the previous formats
  13172. 13172   if (e.keyCode == 13 && !e.shiftKey) {
  13173. 13173   var parent = ed.selection.getStart(), fmt = t._previousFormats;
  13174. 13174  
  13175. 13175   // Parent is an empty block
  13176. 13176   if (!parent.hasChildNodes() && fmt) {
  13177. 13177   parent = dom.getParent(parent, dom.isBlock);
  13178. 13178  
  13179. 13179   if (parent && parent.nodeName != 'LI') {
  13180. 13180   parent.innerHTML = '';
  13181. 13181  
  13182. 13182   if (t._previousFormats) {
  13183. 13183   parent.appendChild(fmt.wrapper);
  13184. 13184   fmt.inner.innerHTML = '\uFEFF';
  13185. 13185   } else
  13186. 13186   parent.innerHTML = '\uFEFF';
  13187. 13187  
  13188. 13188   selection.select(parent, 1);
  13189. 13189   selection.collapse(true);
  13190. 13190   ed.getDoc().execCommand('Delete', false, null);
  13191. 13191   t._previousFormats = 0;
  13192. 13192   }
  13193. 13193   }
  13194. 13194   }
  13195. 13195   });
  13196. 13196   }
  13197. 13197  
  13198. 13198   if (isGecko) {
  13199. 13199   ed.onKeyDown.add(function(ed, e) {
  13200. 13200   if ((e.keyCode == 8 || e.keyCode == 46) && !e.shiftKey)
  13201. 13201   t.backspaceDelete(e, e.keyCode == 8);
  13202. 13202   });
  13203. 13203   }
  13204. 13204   }
  13205. 13205  
  13206. 13206   // Workaround for missing shift+enter support, http://bugs.webkit.org/show_bug.cgi?id=16973
  13207. 13207   if (tinymce.isWebKit) {
  13208. 13208   function insertBr(ed) {
  13209. 13209   var rng = selection.getRng(), br, div = dom.create('div', null, ' '), divYPos, vpHeight = dom.getViewPort(ed.getWin()).h;
  13210. 13210  
  13211. 13211   // Insert BR element
  13212. 13212   rng.insertNode(br = dom.create('br'));
  13213. 13213  
  13214. 13214   // Place caret after BR
  13215. 13215   rng.setStartAfter(br);
  13216. 13216   rng.setEndAfter(br);
  13217. 13217   selection.setRng(rng);
  13218. 13218  
  13219. 13219   // Could not place caret after BR then insert an nbsp entity and move the caret
  13220. 13220   if (selection.getSel().focusNode == br.previousSibling) {
  13221. 13221   selection.select(dom.insertAfter(dom.doc.createTextNode('\u00a0'), br));
  13222. 13222   selection.collapse(TRUE);
  13223. 13223   }
  13224. 13224  
  13225. 13225   // Create a temporary DIV after the BR and get the position as it
  13226. 13226   // seems like getPos() returns 0 for text nodes and BR elements.
  13227. 13227   dom.insertAfter(div, br);
  13228. 13228   divYPos = dom.getPos(div).y;
  13229. 13229   dom.remove(div);
  13230. 13230  
  13231. 13231   // Scroll to new position, scrollIntoView can't be used due to bug: http://bugs.webkit.org/show_bug.cgi?id=16117
  13232. 13232   if (divYPos > vpHeight) // It is not necessary to scroll if the DIV is inside the view port.
  13233. 13233   ed.getWin().scrollTo(0, divYPos);
  13234. 13234   };
  13235. 13235  
  13236. 13236   ed.onKeyPress.add(function(ed, e) {
  13237. 13237   if (e.keyCode == 13 && (e.shiftKey || (s.force_br_newlines && !dom.getParent(selection.getNode(), 'h1,h2,h3,h4,h5,h6,ol,ul')))) {
  13238. 13238   insertBr(ed);
  13239. 13239   Event.cancel(e);
  13240. 13240   }
  13241. 13241   });
  13242. 13242   }
  13243. 13243  
  13244. 13244   // IE specific fixes
  13245. 13245   if (isIE) {
  13246. 13246   // Replaces IE:s auto generated paragraphs with the specified element name
  13247. 13247   if (s.element != 'P') {
  13248. 13248   ed.onKeyPress.add(function(ed, e) {
  13249. 13249   t.lastElm = selection.getNode().nodeName;
  13250. 13250   });
  13251. 13251  
  13252. 13252   ed.onKeyUp.add(function(ed, e) {
  13253. 13253   var bl, n = selection.getNode(), b = ed.getBody();
  13254. 13254  
  13255. 13255   if (b.childNodes.length === 1 && n.nodeName == 'P') {
  13256. 13256   n = dom.rename(n, s.element);
  13257. 13257   selection.select(n);
  13258. 13258   selection.collapse();
  13259. 13259   ed.nodeChanged();
  13260. 13260   } else if (e.keyCode == 13 && !e.shiftKey && t.lastElm != 'P') {
  13261. 13261   bl = dom.getParent(n, 'p');
  13262. 13262  
  13263. 13263   if (bl) {
  13264. 13264   dom.rename(bl, s.element);
  13265. 13265   ed.nodeChanged();
  13266. 13266   }
  13267. 13267   }
  13268. 13268   });
  13269. 13269   }
  13270. 13270   }
  13271. 13271   },
  13272. 13272  
  13273. 13273   find : function(n, t, s) {
  13274. 13274   var ed = this.editor, w = ed.getDoc().createTreeWalker(n, 4, null, FALSE), c = -1;
  13275. 13275  
  13276. 13276   while (n = w.nextNode()) {
  13277. 13277   c++;
  13278. 13278  
  13279. 13279   // Index by node
  13280. 13280   if (t == 0 && n == s)
  13281. 13281   return c;
  13282. 13282  
  13283. 13283   // Node by index
  13284. 13284   if (t == 1 && c == s)
  13285. 13285   return n;
  13286. 13286   }
  13287. 13287  
  13288. 13288   return -1;
  13289. 13289   },
  13290. 13290  
  13291. 13291   forceRoots : function(ed, e) {
  13292. 13292   var t = this, ed = t.editor, b = ed.getBody(), d = ed.getDoc(), se = ed.selection, s = se.getSel(), r = se.getRng(), si = -2, ei, so, eo, tr, c = -0xFFFFFF;
  13293. 13293   var nx, bl, bp, sp, le, nl = b.childNodes, i, n, eid;
  13294. 13294  
  13295. 13295   // Fix for bug #1863847
  13296. 13296   //if (e && e.keyCode == 13)
  13297. 13297   // return TRUE;
  13298. 13298  
  13299. 13299   // Wrap non blocks into blocks
  13300. 13300   for (i = nl.length - 1; i >= 0; i--) {
  13301. 13301   nx = nl[i];
  13302. 13302  
  13303. 13303   // Ignore internal elements
  13304. 13304   if (nx.nodeType === 1 && nx.getAttribute('data-mce-type')) {
  13305. 13305   bl = null;
  13306. 13306   continue;
  13307. 13307   }
  13308. 13308  
  13309. 13309   // Is text or non block element
  13310. 13310   if (nx.nodeType === 3 || (!t.dom.isBlock(nx) && nx.nodeType !== 8 && !/^(script|mce:script|style|mce:style)$/i.test(nx.nodeName))) {
  13311. 13311   if (!bl) {
  13312. 13312   // Create new block but ignore whitespace
  13313. 13313   if (nx.nodeType != 3 || /[^\s]/g.test(nx.nodeValue)) {
  13314. 13314   // Store selection
  13315. 13315   if (si == -2 && r) {
  13316. 13316   if (!isIE || r.setStart) {
  13317. 13317   // If selection is element then mark it
  13318. 13318   if (r.startContainer.nodeType == 1 && (n = r.startContainer.childNodes[r.startOffset]) && n.nodeType == 1) {
  13319. 13319   // Save the id of the selected element
  13320. 13320   eid = n.getAttribute("id");
  13321. 13321   n.setAttribute("id", "__mce");
  13322. 13322   } else {
  13323. 13323   // If element is inside body, might not be the case in contentEdiable mode
  13324. 13324   if (ed.dom.getParent(r.startContainer, function(e) {return e === b;})) {
  13325. 13325   so = r.startOffset;
  13326. 13326   eo = r.endOffset;
  13327. 13327   si = t.find(b, 0, r.startContainer);
  13328. 13328   ei = t.find(b, 0, r.endContainer);
  13329. 13329   }
  13330. 13330   }
  13331. 13331   } else {
  13332. 13332   // Force control range into text range
  13333. 13333   if (r.item) {
  13334. 13334   tr = d.body.createTextRange();
  13335. 13335   tr.moveToElementText(r.item(0));
  13336. 13336   r = tr;
  13337. 13337   }
  13338. 13338  
  13339. 13339   tr = d.body.createTextRange();
  13340. 13340   tr.moveToElementText(b);
  13341. 13341   tr.collapse(1);
  13342. 13342   bp = tr.move('character', c) * -1;
  13343. 13343  
  13344. 13344   tr = r.duplicate();
  13345. 13345   tr.collapse(1);
  13346. 13346   sp = tr.move('character', c) * -1;
  13347. 13347  
  13348. 13348   tr = r.duplicate();
  13349. 13349   tr.collapse(0);
  13350. 13350   le = (tr.move('character', c) * -1) - sp;
  13351. 13351  
  13352. 13352   si = sp - bp;
  13353. 13353   ei = le;
  13354. 13354   }
  13355. 13355   }
  13356. 13356  
  13357. 13357   // Uses replaceChild instead of cloneNode since it removes selected attribute from option elements on IE
  13358. 13358   // See: http://support.microsoft.com/kb/829907
  13359. 13359   bl = ed.dom.create(ed.settings.forced_root_block);
  13360. 13360   nx.parentNode.replaceChild(bl, nx);
  13361. 13361   bl.appendChild(nx);
  13362. 13362   }
  13363. 13363   } else {
  13364. 13364   if (bl.hasChildNodes())
  13365. 13365   bl.insertBefore(nx, bl.firstChild);
  13366. 13366   else
  13367. 13367   bl.appendChild(nx);
  13368. 13368   }
  13369. 13369   } else
  13370. 13370   bl = null; // Time to create new block
  13371. 13371   }
  13372. 13372  
  13373. 13373   // Restore selection
  13374. 13374   if (si != -2) {
  13375. 13375   if (!isIE || r.setStart) {
  13376. 13376   bl = b.getElementsByTagName(ed.settings.element)[0];
  13377. 13377   r = d.createRange();
  13378. 13378  
  13379. 13379   // Select last location or generated block
  13380. 13380   if (si != -1)
  13381. 13381   r.setStart(t.find(b, 1, si), so);
  13382. 13382   else
  13383. 13383   r.setStart(bl, 0);
  13384. 13384  
  13385. 13385   // Select last location or generated block
  13386. 13386   if (ei != -1)
  13387. 13387   r.setEnd(t.find(b, 1, ei), eo);
  13388. 13388   else
  13389. 13389   r.setEnd(bl, 0);
  13390. 13390  
  13391. 13391   if (s) {
  13392. 13392   s.removeAllRanges();
  13393. 13393   s.addRange(r);
  13394. 13394   }
  13395. 13395   } else {
  13396. 13396   try {
  13397. 13397   r = s.createRange();
  13398. 13398   r.moveToElementText(b);
  13399. 13399   r.collapse(1);
  13400. 13400   r.moveStart('character', si);
  13401. 13401   r.moveEnd('character', ei);
  13402. 13402   r.select();
  13403. 13403   } catch (ex) {
  13404. 13404   // Ignore
  13405. 13405   }
  13406. 13406   }
  13407. 13407   } else if ((!isIE || r.setStart) && (n = ed.dom.get('__mce'))) {
  13408. 13408   // Restore the id of the selected element
  13409. 13409   if (eid)
  13410. 13410   n.setAttribute('id', eid);
  13411. 13411   else
  13412. 13412   n.removeAttribute('id');
  13413. 13413  
  13414. 13414   // Move caret before selected element
  13415. 13415   r = d.createRange();
  13416. 13416   r.setStartBefore(n);
  13417. 13417   r.setEndBefore(n);
  13418. 13418   se.setRng(r);
  13419. 13419   }
  13420. 13420   },
  13421. 13421  
  13422. 13422   getParentBlock : function(n) {
  13423. 13423   var d = this.dom;
  13424. 13424  
  13425. 13425   return d.getParent(n, d.isBlock);
  13426. 13426   },
  13427. 13427  
  13428. 13428   insertPara : function(e) {
  13429. 13429   var t = this, ed = t.editor, dom = ed.dom, d = ed.getDoc(), se = ed.settings, s = ed.selection.getSel(), r = s.getRangeAt(0), b = d.body;
  13430. 13430   var rb, ra, dir, sn, so, en, eo, sb, eb, bn, bef, aft, sc, ec, n, vp = dom.getViewPort(ed.getWin()), y, ch, car;
  13431. 13431  
  13432. 13432   ed.undoManager.beforeChange();
  13433. 13433  
  13434. 13434   // If root blocks are forced then use Operas default behavior since it's really good
  13435. 13435  // Removed due to bug: #1853816
  13436. 13436  // if (se.forced_root_block && isOpera)
  13437. 13437  // return TRUE;
  13438. 13438  
  13439. 13439   // Setup before range
  13440. 13440   rb = d.createRange();
  13441. 13441  
  13442. 13442   // If is before the first block element and in body, then move it into first block element
  13443. 13443   rb.setStart(s.anchorNode, s.anchorOffset);
  13444. 13444   rb.collapse(TRUE);
  13445. 13445  
  13446. 13446   // Setup after range
  13447. 13447   ra = d.createRange();
  13448. 13448  
  13449. 13449   // If is before the first block element and in body, then move it into first block element
  13450. 13450   ra.setStart(s.focusNode, s.focusOffset);
  13451. 13451   ra.collapse(TRUE);
  13452. 13452  
  13453. 13453   // Setup start/end points
  13454. 13454   dir = rb.compareBoundaryPoints(rb.START_TO_END, ra) < 0;
  13455. 13455   sn = dir ? s.anchorNode : s.focusNode;
  13456. 13456   so = dir ? s.anchorOffset : s.focusOffset;
  13457. 13457   en = dir ? s.focusNode : s.anchorNode;
  13458. 13458   eo = dir ? s.focusOffset : s.anchorOffset;
  13459. 13459  
  13460. 13460   // If selection is in empty table cell
  13461. 13461   if (sn === en && /^(TD|TH)$/.test(sn.nodeName)) {
  13462. 13462   if (sn.firstChild.nodeName == 'BR')
  13463. 13463   dom.remove(sn.firstChild); // Remove BR
  13464. 13464  
  13465. 13465   // Create two new block elements
  13466. 13466   if (sn.childNodes.length == 0) {
  13467. 13467   ed.dom.add(sn, se.element, null, '<br />');
  13468. 13468   aft = ed.dom.add(sn, se.element, null, '<br />');
  13469. 13469   } else {
  13470. 13470   n = sn.innerHTML;
  13471. 13471   sn.innerHTML = '';
  13472. 13472   ed.dom.add(sn, se.element, null, n);
  13473. 13473   aft = ed.dom.add(sn, se.element, null, '<br />');
  13474. 13474   }
  13475. 13475  
  13476. 13476   // Move caret into the last one
  13477. 13477   r = d.createRange();
  13478. 13478   r.selectNodeContents(aft);
  13479. 13479   r.collapse(1);
  13480. 13480   ed.selection.setRng(r);
  13481. 13481  
  13482. 13482   return FALSE;
  13483. 13483   }
  13484. 13484  
  13485. 13485   // If the caret is in an invalid location in FF we need to move it into the first block
  13486. 13486   if (sn == b && en == b && b.firstChild && ed.dom.isBlock(b.firstChild)) {
  13487. 13487   sn = en = sn.firstChild;
  13488. 13488   so = eo = 0;
  13489. 13489   rb = d.createRange();
  13490. 13490   rb.setStart(sn, 0);
  13491. 13491   ra = d.createRange();
  13492. 13492   ra.setStart(en, 0);
  13493. 13493   }
  13494. 13494  
  13495. 13495   // Never use body as start or end node
  13496. 13496   sn = sn.nodeName == "HTML" ? d.body : sn; // Fix for Opera bug: https://bugs.opera.com/show_bug.cgi?id=273224&comments=yes
  13497. 13497   sn = sn.nodeName == "BODY" ? sn.firstChild : sn;
  13498. 13498   en = en.nodeName == "HTML" ? d.body : en; // Fix for Opera bug: https://bugs.opera.com/show_bug.cgi?id=273224&comments=yes
  13499. 13499   en = en.nodeName == "BODY" ? en.firstChild : en;
  13500. 13500  
  13501. 13501   // Get start and end blocks
  13502. 13502   sb = t.getParentBlock(sn);
  13503. 13503   eb = t.getParentBlock(en);
  13504. 13504   bn = sb ? sb.nodeName : se.element; // Get block name to create
  13505. 13505  
  13506. 13506   // Return inside list use default browser behavior
  13507. 13507   if (n = t.dom.getParent(sb, 'li,pre')) {
  13508. 13508   if (n.nodeName == 'LI')
  13509. 13509   return splitList(ed.selection, t.dom, n);
  13510. 13510  
  13511. 13511   return TRUE;
  13512. 13512   }
  13513. 13513  
  13514. 13514   // If caption or absolute layers then always generate new blocks within
  13515. 13515   if (sb && (sb.nodeName == 'CAPTION' || /absolute|relative|fixed/gi.test(dom.getStyle(sb, 'position', 1)))) {
  13516. 13516   bn = se.element;
  13517. 13517   sb = null;
  13518. 13518   }
  13519. 13519  
  13520. 13520   // If caption or absolute layers then always generate new blocks within
  13521. 13521   if (eb && (eb.nodeName == 'CAPTION' || /absolute|relative|fixed/gi.test(dom.getStyle(sb, 'position', 1)))) {
  13522. 13522   bn = se.element;
  13523. 13523   eb = null;
  13524. 13524   }
  13525. 13525  
  13526. 13526   // Use P instead
  13527. 13527   if (/(TD|TABLE|TH|CAPTION)/.test(bn) || (sb && bn == "DIV" && /left|right/gi.test(dom.getStyle(sb, 'float', 1)))) {
  13528. 13528   bn = se.element;
  13529. 13529   sb = eb = null;
  13530. 13530   }
  13531. 13531  
  13532. 13532   // Setup new before and after blocks
  13533. 13533   bef = (sb && sb.nodeName == bn) ? sb.cloneNode(0) : ed.dom.create(bn);
  13534. 13534   aft = (eb && eb.nodeName == bn) ? eb.cloneNode(0) : ed.dom.create(bn);
  13535. 13535  
  13536. 13536   // Remove id from after clone
  13537. 13537   aft.removeAttribute('id');
  13538. 13538  
  13539. 13539   // Is header and cursor is at the end, then force paragraph under
  13540. 13540   if (/^(H[1-6])$/.test(bn) && isAtEnd(r, sb))
  13541. 13541   aft = ed.dom.create(se.element);
  13542. 13542  
  13543. 13543   // Find start chop node
  13544. 13544   n = sc = sn;
  13545. 13545   do {
  13546. 13546   if (n == b || n.nodeType == 9 || t.dom.isBlock(n) || /(TD|TABLE|TH|CAPTION)/.test(n.nodeName))
  13547. 13547   break;
  13548. 13548  
  13549. 13549   sc = n;
  13550. 13550   } while ((n = n.previousSibling ? n.previousSibling : n.parentNode));
  13551. 13551  
  13552. 13552   // Find end chop node
  13553. 13553   n = ec = en;
  13554. 13554   do {
  13555. 13555   if (n == b || n.nodeType == 9 || t.dom.isBlock(n) || /(TD|TABLE|TH|CAPTION)/.test(n.nodeName))
  13556. 13556   break;
  13557. 13557  
  13558. 13558   ec = n;
  13559. 13559   } while ((n = n.nextSibling ? n.nextSibling : n.parentNode));
  13560. 13560  
  13561. 13561   // Place first chop part into before block element
  13562. 13562   if (sc.nodeName == bn)
  13563. 13563   rb.setStart(sc, 0);
  13564. 13564   else
  13565. 13565   rb.setStartBefore(sc);
  13566. 13566  
  13567. 13567   rb.setEnd(sn, so);
  13568. 13568   bef.appendChild(rb.cloneContents() || d.createTextNode('')); // Empty text node needed for Safari
  13569. 13569  
  13570. 13570   // Place secnd chop part within new block element
  13571. 13571   try {
  13572. 13572   ra.setEndAfter(ec);
  13573. 13573   } catch(ex) {
  13574. 13574   //console.debug(s.focusNode, s.focusOffset);
  13575. 13575   }
  13576. 13576  
  13577. 13577   ra.setStart(en, eo);
  13578. 13578   aft.appendChild(ra.cloneContents() || d.createTextNode('')); // Empty text node needed for Safari
  13579. 13579  
  13580. 13580   // Create range around everything
  13581. 13581   r = d.createRange();
  13582. 13582   if (!sc.previousSibling && sc.parentNode.nodeName == bn) {
  13583. 13583   r.setStartBefore(sc.parentNode);
  13584. 13584   } else {
  13585. 13585   if (rb.startContainer.nodeName == bn && rb.startOffset == 0)
  13586. 13586   r.setStartBefore(rb.startContainer);
  13587. 13587   else
  13588. 13588   r.setStart(rb.startContainer, rb.startOffset);
  13589. 13589   }
  13590. 13590  
  13591. 13591   if (!ec.nextSibling && ec.parentNode.nodeName == bn)
  13592. 13592   r.setEndAfter(ec.parentNode);
  13593. 13593   else
  13594. 13594   r.setEnd(ra.endContainer, ra.endOffset);
  13595. 13595  
  13596. 13596   // Delete and replace it with new block elements
  13597. 13597   r.deleteContents();
  13598. 13598  
  13599. 13599   if (isOpera)
  13600. 13600   ed.getWin().scrollTo(0, vp.y);
  13601. 13601  
  13602. 13602   // Never wrap blocks in blocks
  13603. 13603   if (bef.firstChild && bef.firstChild.nodeName == bn)
  13604. 13604   bef.innerHTML = bef.firstChild.innerHTML;
  13605. 13605  
  13606. 13606   if (aft.firstChild && aft.firstChild.nodeName == bn)
  13607. 13607   aft.innerHTML = aft.firstChild.innerHTML;
  13608. 13608  
  13609. 13609   // Padd empty blocks
  13610. 13610   if (isEmpty(bef))
  13611. 13611   bef.innerHTML = '<br />';
  13612. 13612  
  13613. 13613   function appendStyles(e, en) {
  13614. 13614   var nl = [], nn, n, i;
  13615. 13615  
  13616. 13616   e.innerHTML = '';
  13617. 13617  
  13618. 13618   // Make clones of style elements
  13619. 13619   if (se.keep_styles) {
  13620. 13620   n = en;
  13621. 13621   do {
  13622. 13622   // We only want style specific elements
  13623. 13623   if (/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U)$/.test(n.nodeName)) {
  13624. 13624   nn = n.cloneNode(FALSE);
  13625. 13625   dom.setAttrib(nn, 'id', ''); // Remove ID since it needs to be unique
  13626. 13626   nl.push(nn);
  13627. 13627   }
  13628. 13628   } while (n = n.parentNode);
  13629. 13629   }
  13630. 13630  
  13631. 13631   // Append style elements to aft
  13632. 13632   if (nl.length > 0) {
  13633. 13633   for (i = nl.length - 1, nn = e; i >= 0; i--)
  13634. 13634   nn = nn.appendChild(nl[i]);
  13635. 13635  
  13636. 13636   // Padd most inner style element
  13637. 13637   nl[0].innerHTML = isOpera ? '\u00a0' : '<br />'; // Extra space for Opera so that the caret can move there
  13638. 13638   return nl[0]; // Move caret to most inner element
  13639. 13639   } else
  13640. 13640   e.innerHTML = isOpera ? '\u00a0' : '<br />'; // Extra space for Opera so that the caret can move there
  13641. 13641   };
  13642. 13642  
  13643. 13643   // Fill empty afterblook with current style
  13644. 13644   if (isEmpty(aft))
  13645. 13645   car = appendStyles(aft, en);
  13646. 13646  
  13647. 13647   // Opera needs this one backwards for older versions
  13648. 13648   if (isOpera && parseFloat(opera.version()) < 9.5) {
  13649. 13649   r.insertNode(bef);
  13650. 13650   r.insertNode(aft);
  13651. 13651   } else {
  13652. 13652   r.insertNode(aft);
  13653. 13653   r.insertNode(bef);
  13654. 13654   }
  13655. 13655  
  13656. 13656   // Normalize
  13657. 13657   aft.normalize();
  13658. 13658   bef.normalize();
  13659. 13659  
  13660. 13660   function first(n) {
  13661. 13661   return d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, FALSE).nextNode() || n;
  13662. 13662   };
  13663. 13663  
  13664. 13664   // Move cursor and scroll into view
  13665. 13665   r = d.createRange();
  13666. 13666   r.selectNodeContents(isGecko ? first(car || aft) : car || aft);
  13667. 13667   r.collapse(1);
  13668. 13668   s.removeAllRanges();
  13669. 13669   s.addRange(r);
  13670. 13670  
  13671. 13671   // scrollIntoView seems to scroll the parent window in most browsers now including FF 3.0b4 so it's time to stop using it and do it our selfs
  13672. 13672   y = ed.dom.getPos(aft).y;
  13673. 13673   //ch = aft.clientHeight;
  13674. 13674  
  13675. 13675   // Is element within viewport
  13676. 13676   if (y < vp.y || y + 25 > vp.y + vp.h) {
  13677. 13677   ed.getWin().scrollTo(0, y < vp.y ? y : y - vp.h + 25); // Needs to be hardcoded to roughly one line of text if a huge text block is broken into two blocks
  13678. 13678  
  13679. 13679   /*console.debug(
  13680. 13680   'Element: y=' + y + ', h=' + ch + ', ' +
  13681. 13681   'Viewport: y=' + vp.y + ", h=" + vp.h + ', bottom=' + (vp.y + vp.h)
  13682. 13682   );*/
  13683. 13683   }
  13684. 13684  
  13685. 13685   ed.undoManager.add();
  13686. 13686  
  13687. 13687   return FALSE;
  13688. 13688   },
  13689. 13689  
  13690. 13690   backspaceDelete : function(e, bs) {
  13691. 13691   var t = this, ed = t.editor, b = ed.getBody(), dom = ed.dom, n, se = ed.selection, r = se.getRng(), sc = r.startContainer, n, w, tn, walker;
  13692. 13692  
  13693. 13693   // Delete when caret is behind a element doesn't work correctly on Gecko see #3011651
  13694. 13694   if (!bs && r.collapsed && sc.nodeType == 1 && r.startOffset == sc.childNodes.length) {
  13695. 13695   walker = new tinymce.dom.TreeWalker(sc.lastChild, sc);
  13696. 13696  
  13697. 13697   // Walk the dom backwards until we find a text node
  13698. 13698   for (n = sc.lastChild; n; n = walker.prev()) {
  13699. 13699   if (n.nodeType == 3) {
  13700. 13700   r.setStart(n, n.nodeValue.length);
  13701. 13701   r.collapse(true);
  13702. 13702   se.setRng(r);
  13703. 13703   return;
  13704. 13704   }
  13705. 13705   }
  13706. 13706   }
  13707. 13707  
  13708. 13708   // The caret sometimes gets stuck in Gecko if you delete empty paragraphs
  13709. 13709   // This workaround removes the element by hand and moves the caret to the previous element
  13710. 13710   if (sc && ed.dom.isBlock(sc) && !/^(TD|TH)$/.test(sc.nodeName) && bs) {
  13711. 13711   if (sc.childNodes.length == 0 || (sc.childNodes.length == 1 && sc.firstChild.nodeName == 'BR')) {
  13712. 13712   // Find previous block element
  13713. 13713   n = sc;
  13714. 13714   while ((n = n.previousSibling) && !ed.dom.isBlock(n)) ;
  13715. 13715  
  13716. 13716   if (n) {
  13717. 13717   if (sc != b.firstChild) {
  13718. 13718   // Find last text node
  13719. 13719   w = ed.dom.doc.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, FALSE);
  13720. 13720   while (tn = w.nextNode())
  13721. 13721   n = tn;
  13722. 13722  
  13723. 13723   // Place caret at the end of last text node
  13724. 13724   r = ed.getDoc().createRange();
  13725. 13725   r.setStart(n, n.nodeValue ? n.nodeValue.length : 0);
  13726. 13726   r.setEnd(n, n.nodeValue ? n.nodeValue.length : 0);
  13727. 13727   se.setRng(r);
  13728. 13728  
  13729. 13729   // Remove the target container
  13730. 13730   ed.dom.remove(sc);
  13731. 13731   }
  13732. 13732  
  13733. 13733   return Event.cancel(e);
  13734. 13734   }
  13735. 13735   }
  13736. 13736   }
  13737. 13737   }
  13738. 13738   });
  13739. 13739  })(tinymce);
  13740. 13740  
  13741. 13741  (function(tinymce) {
  13742. 13742   // Shorten names
  13743. 13743   var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, extend = tinymce.extend;
  13744. 13744  
  13745. 13745   tinymce.create('tinymce.ControlManager', {
  13746. 13746   ControlManager : function(ed, s) {
  13747. 13747   var t = this, i;
  13748. 13748  
  13749. 13749   s = s || {};
  13750. 13750   t.editor = ed;
  13751. 13751   t.controls = {};
  13752. 13752   t.onAdd = new tinymce.util.Dispatcher(t);
  13753. 13753   t.onPostRender = new tinymce.util.Dispatcher(t);
  13754. 13754   t.prefix = s.prefix || ed.id + '_';
  13755. 13755   t._cls = {};
  13756. 13756  
  13757. 13757   t.onPostRender.add(function() {
  13758. 13758   each(t.controls, function(c) {
  13759. 13759   c.postRender();
  13760. 13760   });
  13761. 13761   });
  13762. 13762   },
  13763. 13763  
  13764. 13764   get : function(id) {
  13765. 13765   return this.controls[this.prefix + id] || this.controls[id];
  13766. 13766   },
  13767. 13767  
  13768. 13768   setActive : function(id, s) {
  13769. 13769   var c = null;
  13770. 13770  
  13771. 13771   if (c = this.get(id))
  13772. 13772   c.setActive(s);
  13773. 13773  
  13774. 13774   return c;
  13775. 13775   },
  13776. 13776  
  13777. 13777   setDisabled : function(id, s) {
  13778. 13778   var c = null;
  13779. 13779  
  13780. 13780   if (c = this.get(id))
  13781. 13781   c.setDisabled(s);
  13782. 13782  
  13783. 13783   return c;
  13784. 13784   },
  13785. 13785  
  13786. 13786   add : function(c) {
  13787. 13787   var t = this;
  13788. 13788  
  13789. 13789   if (c) {
  13790. 13790   t.controls[c.id] = c;
  13791. 13791   t.onAdd.dispatch(c, t);
  13792. 13792   }
  13793. 13793  
  13794. 13794   return c;
  13795. 13795   },
  13796. 13796  
  13797. 13797   createControl : function(n) {
  13798. 13798   var c, t = this, ed = t.editor;
  13799. 13799  
  13800. 13800   each(ed.plugins, function(p) {
  13801. 13801   if (p.createControl) {
  13802. 13802   c = p.createControl(n, t);
  13803. 13803  
  13804. 13804   if (c)
  13805. 13805   return false;
  13806. 13806   }
  13807. 13807   });
  13808. 13808  
  13809. 13809   switch (n) {
  13810. 13810   case "|":
  13811. 13811   case "separator":
  13812. 13812   return t.createSeparator();
  13813. 13813   }
  13814. 13814  
  13815. 13815   if (!c && ed.buttons && (c = ed.buttons[n]))
  13816. 13816   return t.createButton(n, c);
  13817. 13817  
  13818. 13818   return t.add(c);
  13819. 13819   },
  13820. 13820  
  13821. 13821   createDropMenu : function(id, s, cc) {
  13822. 13822   var t = this, ed = t.editor, c, bm, v, cls;
  13823. 13823  
  13824. 13824   s = extend({
  13825. 13825   'class' : 'mceDropDown',
  13826. 13826   constrain : ed.settings.constrain_menus
  13827. 13827   }, s);
  13828. 13828  
  13829. 13829   s['class'] = s['class'] + ' ' + ed.getParam('skin') + 'Skin';
  13830. 13830   if (v = ed.getParam('skin_variant'))
  13831. 13831   s['class'] += ' ' + ed.getParam('skin') + 'Skin' + v.substring(0, 1).toUpperCase() + v.substring(1);
  13832. 13832  
  13833. 13833   id = t.prefix + id;
  13834. 13834   cls = cc || t._cls.dropmenu || tinymce.ui.DropMenu;
  13835. 13835   c = t.controls[id] = new cls(id, s);
  13836. 13836   c.onAddItem.add(function(c, o) {
  13837. 13837   var s = o.settings;
  13838. 13838  
  13839. 13839   s.title = ed.getLang(s.title, s.title);
  13840. 13840  
  13841. 13841   if (!s.onclick) {
  13842. 13842   s.onclick = function(v) {
  13843. 13843   if (s.cmd)
  13844. 13844   ed.execCommand(s.cmd, s.ui || false, s.value);
  13845. 13845   };
  13846. 13846   }
  13847. 13847   });
  13848. 13848  
  13849. 13849   ed.onRemove.add(function() {
  13850. 13850   c.destroy();
  13851. 13851   });
  13852. 13852  
  13853. 13853   // Fix for bug #1897785, #1898007
  13854. 13854   if (tinymce.isIE) {
  13855. 13855   c.onShowMenu.add(function() {
  13856. 13856   // IE 8 needs focus in order to store away a range with the current collapsed caret location
  13857. 13857   ed.focus();
  13858. 13858  
  13859. 13859   bm = ed.selection.getBookmark(1);
  13860. 13860   });
  13861. 13861  
  13862. 13862   c.onHideMenu.add(function() {
  13863. 13863   if (bm) {
  13864. 13864   ed.selection.moveToBookmark(bm);
  13865. 13865   bm = 0;
  13866. 13866   }
  13867. 13867   });
  13868. 13868   }
  13869. 13869  
  13870. 13870   return t.add(c);
  13871. 13871   },
  13872. 13872  
  13873. 13873   createListBox : function(id, s, cc) {
  13874. 13874   var t = this, ed = t.editor, cmd, c, cls;
  13875. 13875  
  13876. 13876   if (t.get(id))
  13877. 13877   return null;
  13878. 13878  
  13879. 13879   s.title = ed.translate(s.title);
  13880. 13880   s.scope = s.scope || ed;
  13881. 13881  
  13882. 13882   if (!s.onselect) {
  13883. 13883   s.onselect = function(v) {
  13884. 13884   ed.execCommand(s.cmd, s.ui || false, v || s.value);
  13885. 13885   };
  13886. 13886   }
  13887. 13887  
  13888. 13888   s = extend({
  13889. 13889   title : s.title,
  13890. 13890   'class' : 'mce_' + id,
  13891. 13891   scope : s.scope,
  13892. 13892   control_manager : t
  13893. 13893   }, s);
  13894. 13894  
  13895. 13895   id = t.prefix + id;
  13896. 13896  
  13897. 13897   if (ed.settings.use_native_selects)
  13898. 13898   c = new tinymce.ui.NativeListBox(id, s);
  13899. 13899   else {
  13900. 13900   cls = cc || t._cls.listbox || tinymce.ui.ListBox;
  13901. 13901   c = new cls(id, s, ed);
  13902. 13902   }
  13903. 13903  
  13904. 13904   t.controls[id] = c;
  13905. 13905  
  13906. 13906   // Fix focus problem in Safari
  13907. 13907   if (tinymce.isWebKit) {
  13908. 13908   c.onPostRender.add(function(c, n) {
  13909. 13909   // Store bookmark on mousedown
  13910. 13910   Event.add(n, 'mousedown', function() {
  13911. 13911   ed.bookmark = ed.selection.getBookmark(1);
  13912. 13912   });
  13913. 13913  
  13914. 13914   // Restore on focus, since it might be lost
  13915. 13915   Event.add(n, 'focus', function() {
  13916. 13916   ed.selection.moveToBookmark(ed.bookmark);
  13917. 13917   ed.bookmark = null;
  13918. 13918   });
  13919. 13919   });
  13920. 13920   }
  13921. 13921  
  13922. 13922   if (c.hideMenu)
  13923. 13923   ed.onMouseDown.add(c.hideMenu, c);
  13924. 13924  
  13925. 13925   return t.add(c);
  13926. 13926   },
  13927. 13927  
  13928. 13928   createButton : function(id, s, cc) {
  13929. 13929   var t = this, ed = t.editor, o, c, cls;
  13930. 13930  
  13931. 13931   if (t.get(id))
  13932. 13932   return null;
  13933. 13933  
  13934. 13934   s.title = ed.translate(s.title);
  13935. 13935   s.label = ed.translate(s.label);
  13936. 13936   s.scope = s.scope || ed;
  13937. 13937  
  13938. 13938   if (!s.onclick && !s.menu_button) {
  13939. 13939   s.onclick = function() {
  13940. 13940   ed.execCommand(s.cmd, s.ui || false, s.value);
  13941. 13941   };
  13942. 13942   }
  13943. 13943  
  13944. 13944   s = extend({
  13945. 13945   title : s.title,
  13946. 13946   'class' : 'mce_' + id,
  13947. 13947   unavailable_prefix : ed.getLang('unavailable', ''),
  13948. 13948   scope : s.scope,
  13949. 13949   control_manager : t
  13950. 13950   }, s);
  13951. 13951  
  13952. 13952   id = t.prefix + id;
  13953. 13953  
  13954. 13954   if (s.menu_button) {
  13955. 13955   cls = cc || t._cls.menubutton || tinymce.ui.MenuButton;
  13956. 13956   c = new cls(id, s, ed);
  13957. 13957   ed.onMouseDown.add(c.hideMenu, c);
  13958. 13958   } else {
  13959. 13959   cls = t._cls.button || tinymce.ui.Button;
  13960. 13960   c = new cls(id, s);
  13961. 13961   }
  13962. 13962  
  13963. 13963   return t.add(c);
  13964. 13964   },
  13965. 13965  
  13966. 13966   createMenuButton : function(id, s, cc) {
  13967. 13967   s = s || {};
  13968. 13968   s.menu_button = 1;
  13969. 13969  
  13970. 13970   return this.createButton(id, s, cc);
  13971. 13971   },
  13972. 13972  
  13973. 13973   createSplitButton : function(id, s, cc) {
  13974. 13974   var t = this, ed = t.editor, cmd, c, cls;
  13975. 13975  
  13976. 13976   if (t.get(id))
  13977. 13977   return null;
  13978. 13978  
  13979. 13979   s.title = ed.translate(s.title);
  13980. 13980   s.scope = s.scope || ed;
  13981. 13981  
  13982. 13982   if (!s.onclick) {
  13983. 13983   s.onclick = function(v) {
  13984. 13984   ed.execCommand(s.cmd, s.ui || false, v || s.value);
  13985. 13985   };
  13986. 13986   }
  13987. 13987  
  13988. 13988   if (!s.onselect) {
  13989. 13989   s.onselect = function(v) {
  13990. 13990   ed.execCommand(s.cmd, s.ui || false, v || s.value);
  13991. 13991   };
  13992. 13992   }
  13993. 13993  
  13994. 13994   s = extend({
  13995. 13995   title : s.title,
  13996. 13996   'class' : 'mce_' + id,
  13997. 13997   scope : s.scope,
  13998. 13998   control_manager : t
  13999. 13999   }, s);
  14000. 14000  
  14001. 14001   id = t.prefix + id;
  14002. 14002   cls = cc || t._cls.splitbutton || tinymce.ui.SplitButton;
  14003. 14003   c = t.add(new cls(id, s, ed));
  14004. 14004   ed.onMouseDown.add(c.hideMenu, c);
  14005. 14005  
  14006. 14006   return c;
  14007. 14007   },
  14008. 14008  
  14009. 14009   createColorSplitButton : function(id, s, cc) {
  14010. 14010   var t = this, ed = t.editor, cmd, c, cls, bm;
  14011. 14011  
  14012. 14012   if (t.get(id))
  14013. 14013   return null;
  14014. 14014  
  14015. 14015   s.title = ed.translate(s.title);
  14016. 14016   s.scope = s.scope || ed;
  14017. 14017  
  14018. 14018   if (!s.onclick) {
  14019. 14019   s.onclick = function(v) {
  14020. 14020   if (tinymce.isIE)
  14021. 14021   bm = ed.selection.getBookmark(1);
  14022. 14022  
  14023. 14023   ed.execCommand(s.cmd, s.ui || false, v || s.value);
  14024. 14024   };
  14025. 14025   }
  14026. 14026  
  14027. 14027   if (!s.onselect) {
  14028. 14028   s.onselect = function(v) {
  14029. 14029   ed.execCommand(s.cmd, s.ui || false, v || s.value);
  14030. 14030   };
  14031. 14031   }
  14032. 14032  
  14033. 14033   s = extend({
  14034. 14034   title : s.title,
  14035. 14035   'class' : 'mce_' + id,
  14036. 14036   'menu_class' : ed.getParam('skin') + 'Skin',
  14037. 14037   scope : s.scope,
  14038. 14038   more_colors_title : ed.getLang('more_colors')
  14039. 14039   }, s);
  14040. 14040  
  14041. 14041   id = t.prefix + id;
  14042. 14042   cls = cc || t._cls.colorsplitbutton || tinymce.ui.ColorSplitButton;
  14043. 14043   c = new cls(id, s, ed);
  14044. 14044   ed.onMouseDown.add(c.hideMenu, c);
  14045. 14045  
  14046. 14046   // Remove the menu element when the editor is removed
  14047. 14047   ed.onRemove.add(function() {
  14048. 14048   c.destroy();
  14049. 14049   });
  14050. 14050  
  14051. 14051   // Fix for bug #1897785, #1898007
  14052. 14052   if (tinymce.isIE) {
  14053. 14053   c.onShowMenu.add(function() {
  14054. 14054   // IE 8 needs focus in order to store away a range with the current collapsed caret location
  14055. 14055   ed.focus();
  14056. 14056   bm = ed.selection.getBookmark(1);
  14057. 14057   });
  14058. 14058  
  14059. 14059   c.onHideMenu.add(function() {
  14060. 14060   if (bm) {
  14061. 14061   ed.selection.moveToBookmark(bm);
  14062. 14062   bm = 0;
  14063. 14063   }
  14064. 14064   });
  14065. 14065   }
  14066. 14066  
  14067. 14067   return t.add(c);
  14068. 14068   },
  14069. 14069  
  14070. 14070   createToolbar : function(id, s, cc) {
  14071. 14071   var c, t = this, cls;
  14072. 14072  
  14073. 14073   id = t.prefix + id;
  14074. 14074   cls = cc || t._cls.toolbar || tinymce.ui.Toolbar;
  14075. 14075   c = new cls(id, s, t.editor);
  14076. 14076  
  14077. 14077   if (t.get(id))
  14078. 14078   return null;
  14079. 14079  
  14080. 14080   return t.add(c);
  14081. 14081   },
  14082. 14082  
  14083. 14083   createToolbarGroup : function(id, s, cc) {
  14084. 14084   var c, t = this, cls;
  14085. 14085   id = t.prefix + id;
  14086. 14086   cls = cc || this._cls.toolbarGroup || tinymce.ui.ToolbarGroup;
  14087. 14087   c = new cls(id, s, t.editor);
  14088. 14088  
  14089. 14089   if (t.get(id))
  14090. 14090   return null;
  14091. 14091  
  14092. 14092   return t.add(c);
  14093. 14093   },
  14094. 14094  
  14095. 14095   createSeparator : function(cc) {
  14096. 14096   var cls = cc || this._cls.separator || tinymce.ui.Separator;
  14097. 14097  
  14098. 14098   return new cls();
  14099. 14099   },
  14100. 14100  
  14101. 14101   setControlType : function(n, c) {
  14102. 14102   return this._cls[n.toLowerCase()] = c;
  14103. 14103   },
  14104. 14104  
  14105. 14105   destroy : function() {
  14106. 14106   each(this.controls, function(c) {
  14107. 14107   c.destroy();
  14108. 14108   });
  14109. 14109  
  14110. 14110   this.controls = null;
  14111. 14111   }
  14112. 14112   });
  14113. 14113  })(tinymce);
  14114. 14114  
  14115. 14115  (function(tinymce) {
  14116. 14116   var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each, isIE = tinymce.isIE, isOpera = tinymce.isOpera;
  14117. 14117  
  14118. 14118   tinymce.create('tinymce.WindowManager', {
  14119. 14119   WindowManager : function(ed) {
  14120. 14120   var t = this;
  14121. 14121  
  14122. 14122   t.editor = ed;
  14123. 14123   t.onOpen = new Dispatcher(t);
  14124. 14124   t.onClose = new Dispatcher(t);
  14125. 14125   t.params = {};
  14126. 14126   t.features = {};
  14127. 14127   },
  14128. 14128  
  14129. 14129   open : function(s, p) {
  14130. 14130   var t = this, f = '', x, y, mo = t.editor.settings.dialog_type == 'modal', w, sw, sh, vp = tinymce.DOM.getViewPort(), u;
  14131. 14131  
  14132. 14132   // Default some options
  14133. 14133   s = s || {};
  14134. 14134   p = p || {};
  14135. 14135   sw = isOpera ? vp.w : screen.width; // Opera uses windows inside the Opera window
  14136. 14136   sh = isOpera ? vp.h : screen.height;
  14137. 14137   s.name = s.name || 'mc_' + new Date().getTime();
  14138. 14138   s.width = parseInt(s.width || 320);
  14139. 14139   s.height = parseInt(s.height || 240);
  14140. 14140   s.resizable = true;
  14141. 14141   s.left = s.left || parseInt(sw / 2.0) - (s.width / 2.0);
  14142. 14142   s.top = s.top || parseInt(sh / 2.0) - (s.height / 2.0);
  14143. 14143   p.inline = false;
  14144. 14144   p.mce_width = s.width;
  14145. 14145   p.mce_height = s.height;
  14146. 14146   p.mce_auto_focus = s.auto_focus;
  14147. 14147  
  14148. 14148   if (mo) {
  14149. 14149   if (isIE) {
  14150. 14150   s.center = true;
  14151. 14151   s.help = false;
  14152. 14152   s.dialogWidth = s.width + 'px';
  14153. 14153   s.dialogHeight = s.height + 'px';
  14154. 14154   s.scroll = s.scrollbars || false;
  14155. 14155   }
  14156. 14156   }
  14157. 14157  
  14158. 14158   // Build features string
  14159. 14159   each(s, function(v, k) {
  14160. 14160   if (tinymce.is(v, 'boolean'))
  14161. 14161   v = v ? 'yes' : 'no';
  14162. 14162  
  14163. 14163   if (!/^(name|url)$/.test(k)) {
  14164. 14164   if (isIE && mo)
  14165. 14165   f += (f ? ';' : '') + k + ':' + v;
  14166. 14166   else
  14167. 14167   f += (f ? ',' : '') + k + '=' + v;
  14168. 14168   }
  14169. 14169   });
  14170. 14170  
  14171. 14171   t.features = s;
  14172. 14172   t.params = p;
  14173. 14173   t.onOpen.dispatch(t, s, p);
  14174. 14174  
  14175. 14175   u = s.url || s.file;
  14176. 14176   u = tinymce._addVer(u);
  14177. 14177  
  14178. 14178   try {
  14179. 14179   if (isIE && mo) {
  14180. 14180   w = 1;
  14181. 14181   window.showModalDialog(u, window, f);
  14182. 14182   } else
  14183. 14183   w = window.open(u, s.name, f);
  14184. 14184   } catch (ex) {
  14185. 14185   // Ignore
  14186. 14186   }
  14187. 14187  
  14188. 14188   if (!w)
  14189. 14189   alert(t.editor.getLang('popup_blocked'));
  14190. 14190   },
  14191. 14191  
  14192. 14192   close : function(w) {
  14193. 14193   w.close();
  14194. 14194   this.onClose.dispatch(this);
  14195. 14195   },
  14196. 14196  
  14197. 14197   createInstance : function(cl, a, b, c, d, e) {
  14198. 14198   var f = tinymce.resolve(cl);
  14199. 14199  
  14200. 14200   return new f(a, b, c, d, e);
  14201. 14201   },
  14202. 14202  
  14203. 14203   confirm : function(t, cb, s, w) {
  14204. 14204   w = w || window;
  14205. 14205  
  14206. 14206   cb.call(s || this, w.confirm(this._decode(this.editor.getLang(t, t))));
  14207. 14207   },
  14208. 14208  
  14209. 14209   alert : function(tx, cb, s, w) {
  14210. 14210   var t = this;
  14211. 14211  
  14212. 14212   w = w || window;
  14213. 14213   w.alert(t._decode(t.editor.getLang(tx, tx)));
  14214. 14214  
  14215. 14215   if (cb)
  14216. 14216   cb.call(s || t);
  14217. 14217   },
  14218. 14218  
  14219. 14219   resizeBy : function(dw, dh, win) {
  14220. 14220   win.resizeBy(dw, dh);
  14221. 14221   },
  14222. 14222  
  14223. 14223   // Internal functions
  14224. 14224  
  14225. 14225   _decode : function(s) {
  14226. 14226   return tinymce.DOM.decode(s).replace(/\\n/g, '\n');
  14227. 14227   }
  14228. 14228   });
  14229. 14229  }(tinymce));
  14230. 14230  (function(tinymce) {
  14231. 14231   tinymce.Formatter = function(ed) {
  14232. 14232   var formats = {},
  14233. 14233   each = tinymce.each,
  14234. 14234   dom = ed.dom,
  14235. 14235   selection = ed.selection,
  14236. 14236   TreeWalker = tinymce.dom.TreeWalker,
  14237. 14237   rangeUtils = new tinymce.dom.RangeUtils(dom),
  14238. 14238   isValid = ed.schema.isValidChild,
  14239. 14239   isBlock = dom.isBlock,
  14240. 14240   forcedRootBlock = ed.settings.forced_root_block,
  14241. 14241   nodeIndex = dom.nodeIndex,
  14242. 14242   INVISIBLE_CHAR = '\uFEFF',
  14243. 14243   MCE_ATTR_RE = /^(src|href|style)$/,
  14244. 14244   FALSE = false,
  14245. 14245   TRUE = true,
  14246. 14246   undefined,
  14247. 14247   pendingFormats = {apply : [], remove : []};
  14248. 14248  
  14249. 14249   function isArray(obj) {
  14250. 14250   return obj instanceof Array;
  14251. 14251   };
  14252. 14252  
  14253. 14253   function getParents(node, selector) {
  14254. 14254   return dom.getParents(node, selector, dom.getRoot());
  14255. 14255   };
  14256. 14256  
  14257. 14257   function isCaretNode(node) {
  14258. 14258   return node.nodeType === 1 && (node.face === 'mceinline' || node.style.fontFamily === 'mceinline');
  14259. 14259   };
  14260. 14260  
  14261. 14261   // Public functions
  14262. 14262  
  14263. 14263   function get(name) {
  14264. 14264   return name ? formats[name] : formats;
  14265. 14265   };
  14266. 14266  
  14267. 14267   function register(name, format) {
  14268. 14268   if (name) {
  14269. 14269   if (typeof(name) !== 'string') {
  14270. 14270   each(name, function(format, name) {
  14271. 14271   register(name, format);
  14272. 14272   });
  14273. 14273   } else {
  14274. 14274   // Force format into array and add it to internal collection
  14275. 14275   format = format.length ? format : [format];
  14276. 14276  
  14277. 14277   each(format, function(format) {
  14278. 14278   // Set deep to false by default on selector formats this to avoid removing
  14279. 14279   // alignment on images inside paragraphs when alignment is changed on paragraphs
  14280. 14280   if (format.deep === undefined)
  14281. 14281   format.deep = !format.selector;
  14282. 14282  
  14283. 14283   // Default to true
  14284. 14284   if (format.split === undefined)
  14285. 14285   format.split = !format.selector || format.inline;
  14286. 14286  
  14287. 14287   // Default to true
  14288. 14288   if (format.remove === undefined && format.selector && !format.inline)
  14289. 14289   format.remove = 'none';
  14290. 14290  
  14291. 14291   // Mark format as a mixed format inline + block level
  14292. 14292   if (format.selector && format.inline) {
  14293. 14293   format.mixed = true;
  14294. 14294   format.block_expand = true;
  14295. 14295   }
  14296. 14296  
  14297. 14297   // Split classes if needed
  14298. 14298   if (typeof(format.classes) === 'string')
  14299. 14299   format.classes = format.classes.split(/\s+/);
  14300. 14300   });
  14301. 14301  
  14302. 14302   formats[name] = format;
  14303. 14303   }
  14304. 14304   }
  14305. 14305   };
  14306. 14306  
  14307. 14307   var getTextDecoration = function(node) {
  14308. 14308   var decoration;
  14309. 14309  
  14310. 14310   ed.dom.getParent(node, function(n) {
  14311. 14311   decoration = ed.dom.getStyle(n, 'text-decoration');
  14312. 14312   return decoration && decoration !== 'none';
  14313. 14313   });
  14314. 14314  
  14315. 14315   return decoration;
  14316. 14316   };
  14317. 14317  
  14318. 14318   var processUnderlineAndColor = function(node) {
  14319. 14319   var textDecoration;
  14320. 14320   if (node.nodeType === 1 && node.parentNode && node.parentNode.nodeType === 1) {
  14321. 14321   textDecoration = getTextDecoration(node.parentNode);
  14322. 14322   if (ed.dom.getStyle(node, 'color') && textDecoration) {
  14323. 14323   ed.dom.setStyle(node, 'text-decoration', textDecoration);
  14324. 14324   } else if (ed.dom.getStyle(node, 'textdecoration') === textDecoration) {
  14325. 14325   ed.dom.setStyle(node, 'text-decoration', null);
  14326. 14326   }
  14327. 14327   }
  14328. 14328   };
  14329. 14329  
  14330. 14330   function apply(name, vars, node) {
  14331. 14331   var formatList = get(name), format = formatList[0], bookmark, rng, i, isCollapsed = selection.isCollapsed();
  14332. 14332  
  14333. 14333   function moveStart(rng) {
  14334. 14334   var container = rng.startContainer,
  14335. 14335   offset = rng.startOffset,
  14336. 14336   walker, node;
  14337. 14337  
  14338. 14338   // Move startContainer/startOffset in to a suitable node
  14339. 14339   if (container.nodeType == 1 || container.nodeValue === "") {
  14340. 14340   container = container.nodeType == 1 ? container.childNodes[offset] : container;
  14341. 14341  
  14342. 14342   // Might fail if the offset is behind the last element in it's container
  14343. 14343   if (container) {
  14344. 14344   walker = new TreeWalker(container, container.parentNode);
  14345. 14345   for (node = walker.current(); node; node = walker.next()) {
  14346. 14346   if (node.nodeType == 3 && !isWhiteSpaceNode(node)) {
  14347. 14347   rng.setStart(node, 0);
  14348. 14348   break;
  14349. 14349   }
  14350. 14350   }
  14351. 14351   }
  14352. 14352   }
  14353. 14353  
  14354. 14354   return rng;
  14355. 14355   };
  14356. 14356  
  14357. 14357   function setElementFormat(elm, fmt) {
  14358. 14358   fmt = fmt || format;
  14359. 14359  
  14360. 14360   if (elm) {
  14361. 14361   each(fmt.styles, function(value, name) {
  14362. 14362   dom.setStyle(elm, name, replaceVars(value, vars));
  14363. 14363   });
  14364. 14364  
  14365. 14365   each(fmt.attributes, function(value, name) {
  14366. 14366   dom.setAttrib(elm, name, replaceVars(value, vars));
  14367. 14367   });
  14368. 14368  
  14369. 14369   each(fmt.classes, function(value) {
  14370. 14370   value = replaceVars(value, vars);
  14371. 14371  
  14372. 14372   if (!dom.hasClass(elm, value))
  14373. 14373   dom.addClass(elm, value);
  14374. 14374   });
  14375. 14375   }
  14376. 14376   };
  14377. 14377  
  14378. 14378   function applyRngStyle(rng) {
  14379. 14379   var newWrappers = [], wrapName, wrapElm;
  14380. 14380  
  14381. 14381   // Setup wrapper element
  14382. 14382   wrapName = format.inline || format.block;
  14383. 14383   wrapElm = dom.create(wrapName);
  14384. 14384   setElementFormat(wrapElm);
  14385. 14385  
  14386. 14386   rangeUtils.walk(rng, function(nodes) {
  14387. 14387   var currentWrapElm;
  14388. 14388  
  14389. 14389   function process(node) {
  14390. 14390   var nodeName = node.nodeName.toLowerCase(), parentName = node.parentNode.nodeName.toLowerCase(), found;
  14391. 14391  
  14392. 14392   // Stop wrapping on br elements
  14393. 14393   if (isEq(nodeName, 'br')) {
  14394. 14394   currentWrapElm = 0;
  14395. 14395  
  14396. 14396   // Remove any br elements when we wrap things
  14397. 14397   if (format.block)
  14398. 14398   dom.remove(node);
  14399. 14399  
  14400. 14400   return;
  14401. 14401   }
  14402. 14402  
  14403. 14403   // If node is wrapper type
  14404. 14404   if (format.wrapper && matchNode(node, name, vars)) {
  14405. 14405   currentWrapElm = 0;
  14406. 14406   return;
  14407. 14407   }
  14408. 14408  
  14409. 14409   // Can we rename the block
  14410. 14410   if (format.block && !format.wrapper && isTextBlock(nodeName)) {
  14411. 14411   node = dom.rename(node, wrapName);
  14412. 14412   setElementFormat(node);
  14413. 14413   newWrappers.push(node);
  14414. 14414   currentWrapElm = 0;
  14415. 14415   return;
  14416. 14416   }
  14417. 14417  
  14418. 14418   // Handle selector patterns
  14419. 14419   if (format.selector) {
  14420. 14420   // Look for matching formats
  14421. 14421   each(formatList, function(format) {
  14422. 14422   // Check collapsed state if it exists
  14423. 14423   if ('collapsed' in format && format.collapsed !== isCollapsed) {
  14424. 14424   return;
  14425. 14425   }
  14426. 14426  
  14427. 14427   if (dom.is(node, format.selector) && !isCaretNode(node)) {
  14428. 14428   setElementFormat(node, format);
  14429. 14429   found = true;
  14430. 14430   }
  14431. 14431   });
  14432. 14432  
  14433. 14433   // Continue processing if a selector match wasn't found and a inline element is defined
  14434. 14434   if (!format.inline || found) {
  14435. 14435   currentWrapElm = 0;
  14436. 14436   return;
  14437. 14437   }
  14438. 14438   }
  14439. 14439  
  14440. 14440   // Is it valid to wrap this item
  14441. 14441   if (isValid(wrapName, nodeName) && isValid(parentName, wrapName) &&
  14442. 14442   !(node.nodeType === 3 && node.nodeValue.length === 1 && node.nodeValue.charCodeAt(0) === 65279)) {
  14443. 14443   // Start wrapping
  14444. 14444   if (!currentWrapElm) {
  14445. 14445   // Wrap the node
  14446. 14446   currentWrapElm = wrapElm.cloneNode(FALSE);
  14447. 14447   node.parentNode.insertBefore(currentWrapElm, node);
  14448. 14448   newWrappers.push(currentWrapElm);
  14449. 14449   }
  14450. 14450  
  14451. 14451   currentWrapElm.appendChild(node);
  14452. 14452   } else {
  14453. 14453   // Start a new wrapper for possible children
  14454. 14454   currentWrapElm = 0;
  14455. 14455  
  14456. 14456   each(tinymce.grep(node.childNodes), process);
  14457. 14457  
  14458. 14458   // End the last wrapper
  14459. 14459   currentWrapElm = 0;
  14460. 14460   }
  14461. 14461   };
  14462. 14462  
  14463. 14463   // Process siblings from range
  14464. 14464   each(nodes, process);
  14465. 14465   });
  14466. 14466  
  14467. 14467   // Wrap links inside as well, for example color inside a link when the wrapper is around the link
  14468. 14468   if (format.wrap_links === false) {
  14469. 14469   each(newWrappers, function(node) {
  14470. 14470   function process(node) {
  14471. 14471   var i, currentWrapElm, children;
  14472. 14472  
  14473. 14473   if (node.nodeName === 'A') {
  14474. 14474   currentWrapElm = wrapElm.cloneNode(FALSE);
  14475. 14475   newWrappers.push(currentWrapElm);
  14476. 14476  
  14477. 14477   children = tinymce.grep(node.childNodes);
  14478. 14478   for (i = 0; i < children.length; i++)
  14479. 14479   currentWrapElm.appendChild(children[i]);
  14480. 14480  
  14481. 14481   node.appendChild(currentWrapElm);
  14482. 14482   }
  14483. 14483  
  14484. 14484   each(tinymce.grep(node.childNodes), process);
  14485. 14485   };
  14486. 14486  
  14487. 14487   process(node);
  14488. 14488   });
  14489. 14489   }
  14490. 14490  
  14491. 14491   // Cleanup
  14492. 14492   each(newWrappers, function(node) {
  14493. 14493   var childCount;
  14494. 14494  
  14495. 14495   function getChildCount(node) {
  14496. 14496   var count = 0;
  14497. 14497  
  14498. 14498   each(node.childNodes, function(node) {
  14499. 14499   if (!isWhiteSpaceNode(node) && !isBookmarkNode(node))
  14500. 14500   count++;
  14501. 14501   });
  14502. 14502  
  14503. 14503   return count;
  14504. 14504   };
  14505. 14505  
  14506. 14506   function mergeStyles(node) {
  14507. 14507   var child, clone;
  14508. 14508  
  14509. 14509   each(node.childNodes, function(node) {
  14510. 14510   if (node.nodeType == 1 && !isBookmarkNode(node) && !isCaretNode(node)) {
  14511. 14511   child = node;
  14512. 14512   return FALSE; // break loop
  14513. 14513   }
  14514. 14514   });
  14515. 14515  
  14516. 14516   // If child was found and of the same type as the current node
  14517. 14517   if (child && matchName(child, format)) {
  14518. 14518   clone = child.cloneNode(FALSE);
  14519. 14519   setElementFormat(clone);
  14520. 14520  
  14521. 14521   dom.replace(clone, node, TRUE);
  14522. 14522   dom.remove(child, 1);
  14523. 14523   }
  14524. 14524  
  14525. 14525   return clone || node;
  14526. 14526   };
  14527. 14527  
  14528. 14528   childCount = getChildCount(node);
  14529. 14529  
  14530. 14530   // Remove empty nodes but only if there is multiple wrappers and they are not block
  14531. 14531   // elements so never remove single <h1></h1> since that would remove the currrent empty block element where the caret is at
  14532. 14532   if ((newWrappers.length > 1 || !isBlock(node)) && childCount === 0) {
  14533. 14533   dom.remove(node, 1);
  14534. 14534   return;
  14535. 14535   }
  14536. 14536  
  14537. 14537   if (format.inline || format.wrapper) {
  14538. 14538   // Merges the current node with it's children of similar type to reduce the number of elements
  14539. 14539   if (!format.exact && childCount === 1)
  14540. 14540   node = mergeStyles(node);
  14541. 14541  
  14542. 14542   // Remove/merge children
  14543. 14543   each(formatList, function(format) {
  14544. 14544   // Merge all children of similar type will move styles from child to parent
  14545. 14545   // this: <span style="color:red"><b><span style="color:red; font-size:10px">text</span></b></span>
  14546. 14546   // will become: <span style="color:red"><b><span style="font-size:10px">text</span></b></span>
  14547. 14547   each(dom.select(format.inline, node), function(child) {
  14548. 14548   var parent;
  14549. 14549  
  14550. 14550   // When wrap_links is set to false we don't want
  14551. 14551   // to remove the format on children within links
  14552. 14552   if (format.wrap_links === false) {
  14553. 14553   parent = child.parentNode;
  14554. 14554  
  14555. 14555   do {
  14556. 14556   if (parent.nodeName === 'A')
  14557. 14557   return;
  14558. 14558   } while (parent = parent.parentNode);
  14559. 14559   }
  14560. 14560  
  14561. 14561   removeFormat(format, vars, child, format.exact ? child : null);
  14562. 14562   });
  14563. 14563   });
  14564. 14564  
  14565. 14565   // Remove child if direct parent is of same type
  14566. 14566   if (matchNode(node.parentNode, name, vars)) {
  14567. 14567   dom.remove(node, 1);
  14568. 14568   node = 0;
  14569. 14569   return TRUE;
  14570. 14570   }
  14571. 14571  
  14572. 14572   // Look for parent with similar style format
  14573. 14573   if (format.merge_with_parents) {
  14574. 14574   dom.getParent(node.parentNode, function(parent) {
  14575. 14575   if (matchNode(parent, name, vars)) {
  14576. 14576   dom.remove(node, 1);
  14577. 14577   node = 0;
  14578. 14578   return TRUE;
  14579. 14579   }
  14580. 14580   });
  14581. 14581   }
  14582. 14582  
  14583. 14583   // Merge next and previous siblings if they are similar <b>text</b><b>text</b> becomes <b>texttext</b>
  14584. 14584   if (node) {
  14585. 14585   node = mergeSiblings(getNonWhiteSpaceSibling(node), node);
  14586. 14586   node = mergeSiblings(node, getNonWhiteSpaceSibling(node, TRUE));
  14587. 14587   }
  14588. 14588   }
  14589. 14589   });
  14590. 14590   };
  14591. 14591  
  14592. 14592   if (format) {
  14593. 14593   if (node) {
  14594. 14594   rng = dom.createRng();
  14595. 14595  
  14596. 14596   rng.setStartBefore(node);
  14597. 14597   rng.setEndAfter(node);
  14598. 14598  
  14599. 14599   applyRngStyle(expandRng(rng, formatList));
  14600. 14600   } else {
  14601. 14601   if (!isCollapsed || !format.inline || dom.select('td.mceSelected,th.mceSelected').length) {
  14602. 14602   // Obtain selection node before selection is unselected by applyRngStyle()
  14603. 14603   var curSelNode = ed.selection.getNode();
  14604. 14604  
  14605. 14605   // Apply formatting to selection
  14606. 14606   bookmark = selection.getBookmark();
  14607. 14607   applyRngStyle(expandRng(selection.getRng(TRUE), formatList));
  14608. 14608  
  14609. 14609   // Colored nodes should be underlined so that the color of the underline matches the text color.
  14610. 14610   if (format.styles && (format.styles.color || format.styles.textDecoration)) {
  14611. 14611   tinymce.walk(curSelNode, processUnderlineAndColor, 'childNodes');
  14612. 14612   processUnderlineAndColor(curSelNode);
  14613. 14613   }
  14614. 14614  
  14615. 14615   selection.moveToBookmark(bookmark);
  14616. 14616   selection.setRng(moveStart(selection.getRng(TRUE)));
  14617. 14617   ed.nodeChanged();
  14618. 14618   } else
  14619. 14619   performCaretAction('apply', name, vars);
  14620. 14620   }
  14621. 14621   }
  14622. 14622   };
  14623. 14623  
  14624. 14624   function remove(name, vars, node) {
  14625. 14625   var formatList = get(name), format = formatList[0], bookmark, i, rng;
  14626. 14626  
  14627. 14627   function moveStart(rng) {
  14628. 14628   var container = rng.startContainer,
  14629. 14629   offset = rng.startOffset,
  14630. 14630   walker, node, nodes, tmpNode;
  14631. 14631  
  14632. 14632   // Convert text node into index if possible
  14633. 14633   if (container.nodeType == 3 && offset >= container.nodeValue.length - 1) {
  14634. 14634   container = container.parentNode;
  14635. 14635   offset = nodeIndex(container) + 1;
  14636. 14636   }
  14637. 14637  
  14638. 14638   // Move startContainer/startOffset in to a suitable node
  14639. 14639   if (container.nodeType == 1) {
  14640. 14640   nodes = container.childNodes;
  14641. 14641   container = nodes[Math.min(offset, nodes.length - 1)];
  14642. 14642   walker = new TreeWalker(container);
  14643. 14643  
  14644. 14644   // If offset is at end of the parent node walk to the next one
  14645. 14645   if (offset > nodes.length - 1)
  14646. 14646   walker.next();
  14647. 14647  
  14648. 14648   for (node = walker.current(); node; node = walker.next()) {
  14649. 14649   if (node.nodeType == 3 && !isWhiteSpaceNode(node)) {
  14650. 14650   // IE has a "neat" feature where it moves the start node into the closest element
  14651. 14651   // we can avoid this by inserting an element before it and then remove it after we set the selection
  14652. 14652   tmpNode = dom.create('a', null, INVISIBLE_CHAR);
  14653. 14653   node.parentNode.insertBefore(tmpNode, node);
  14654. 14654  
  14655. 14655   // Set selection and remove tmpNode
  14656. 14656   rng.setStart(node, 0);
  14657. 14657   selection.setRng(rng);
  14658. 14658   dom.remove(tmpNode);
  14659. 14659  
  14660. 14660   return;
  14661. 14661   }
  14662. 14662   }
  14663. 14663   }
  14664. 14664   };
  14665. 14665  
  14666. 14666   // Merges the styles for each node
  14667. 14667   function process(node) {
  14668. 14668   var children, i, l;
  14669. 14669  
  14670. 14670   // Grab the children first since the nodelist might be changed
  14671. 14671   children = tinymce.grep(node.childNodes);
  14672. 14672  
  14673. 14673   // Process current node
  14674. 14674   for (i = 0, l = formatList.length; i < l; i++) {
  14675. 14675   if (removeFormat(formatList[i], vars, node, node))
  14676. 14676   break;
  14677. 14677   }
  14678. 14678  
  14679. 14679   // Process the children
  14680. 14680   if (format.deep) {
  14681. 14681   for (i = 0, l = children.length; i < l; i++)
  14682. 14682   process(children[i]);
  14683. 14683   }
  14684. 14684   };
  14685. 14685  
  14686. 14686   function findFormatRoot(container) {
  14687. 14687   var formatRoot;
  14688. 14688  
  14689. 14689   // Find format root
  14690. 14690   each(getParents(container.parentNode).reverse(), function(parent) {
  14691. 14691   var format;
  14692. 14692  
  14693. 14693   // Find format root element
  14694. 14694   if (!formatRoot && parent.id != '_start' && parent.id != '_end') {
  14695. 14695   // Is the node matching the format we are looking for
  14696. 14696   format = matchNode(parent, name, vars);
  14697. 14697   if (format && format.split !== false)
  14698. 14698   formatRoot = parent;
  14699. 14699   }
  14700. 14700   });
  14701. 14701  
  14702. 14702   return formatRoot;
  14703. 14703   };
  14704. 14704  
  14705. 14705   function wrapAndSplit(format_root, container, target, split) {
  14706. 14706   var parent, clone, lastClone, firstClone, i, formatRootParent;
  14707. 14707  
  14708. 14708   // Format root found then clone formats and split it
  14709. 14709   if (format_root) {
  14710. 14710   formatRootParent = format_root.parentNode;
  14711. 14711  
  14712. 14712   for (parent = container.parentNode; parent && parent != formatRootParent; parent = parent.parentNode) {
  14713. 14713   clone = parent.cloneNode(FALSE);
  14714. 14714  
  14715. 14715   for (i = 0; i < formatList.length; i++) {
  14716. 14716   if (removeFormat(formatList[i], vars, clone, clone)) {
  14717. 14717   clone = 0;
  14718. 14718   break;
  14719. 14719   }
  14720. 14720   }
  14721. 14721  
  14722. 14722   // Build wrapper node
  14723. 14723   if (clone) {
  14724. 14724   if (lastClone)
  14725. 14725   clone.appendChild(lastClone);
  14726. 14726  
  14727. 14727   if (!firstClone)
  14728. 14728   firstClone = clone;
  14729. 14729  
  14730. 14730   lastClone = clone;
  14731. 14731   }
  14732. 14732   }
  14733. 14733  
  14734. 14734   // Never split block elements if the format is mixed
  14735. 14735   if (split && (!format.mixed || !isBlock(format_root)))
  14736. 14736   container = dom.split(format_root, container);
  14737. 14737  
  14738. 14738   // Wrap container in cloned formats
  14739. 14739   if (lastClone) {
  14740. 14740   target.parentNode.insertBefore(lastClone, target);
  14741. 14741   firstClone.appendChild(target);
  14742. 14742   }
  14743. 14743   }
  14744. 14744  
  14745. 14745   return container;
  14746. 14746   };
  14747. 14747  
  14748. 14748   function splitToFormatRoot(container) {
  14749. 14749   return wrapAndSplit(findFormatRoot(container), container, container, true);
  14750. 14750   };
  14751. 14751  
  14752. 14752   function unwrap(start) {
  14753. 14753   var node = dom.get(start ? '_start' : '_end'),
  14754. 14754   out = node[start ? 'firstChild' : 'lastChild'];
  14755. 14755  
  14756. 14756   // If the end is placed within the start the result will be removed
  14757. 14757   // So this checks if the out node is a bookmark node if it is it
  14758. 14758   // checks for another more suitable node
  14759. 14759   if (isBookmarkNode(out))
  14760. 14760   out = out[start ? 'firstChild' : 'lastChild'];
  14761. 14761  
  14762. 14762   dom.remove(node, true);
  14763. 14763  
  14764. 14764   return out;
  14765. 14765   };
  14766. 14766  
  14767. 14767   function removeRngStyle(rng) {
  14768. 14768   var startContainer, endContainer;
  14769. 14769  
  14770. 14770   rng = expandRng(rng, formatList, TRUE);
  14771. 14771  
  14772. 14772   if (format.split) {
  14773. 14773   startContainer = getContainer(rng, TRUE);
  14774. 14774   endContainer = getContainer(rng);
  14775. 14775  
  14776. 14776   if (startContainer != endContainer) {
  14777. 14777   // Wrap start/end nodes in span element since these might be cloned/moved
  14778. 14778   startContainer = wrap(startContainer, 'span', {id : '_start', 'data-mce-type' : 'bookmark'});
  14779. 14779   endContainer = wrap(endContainer, 'span', {id : '_end', 'data-mce-type' : 'bookmark'});
  14780. 14780  
  14781. 14781   // Split start/end
  14782. 14782   splitToFormatRoot(startContainer);
  14783. 14783   splitToFormatRoot(endContainer);
  14784. 14784  
  14785. 14785   // Unwrap start/end to get real elements again
  14786. 14786   startContainer = unwrap(TRUE);
  14787. 14787   endContainer = unwrap();
  14788. 14788   } else
  14789. 14789   startContainer = endContainer = splitToFormatRoot(startContainer);
  14790. 14790  
  14791. 14791   // Update range positions since they might have changed after the split operations
  14792. 14792   rng.startContainer = startContainer.parentNode;
  14793. 14793   rng.startOffset = nodeIndex(startContainer);
  14794. 14794   rng.endContainer = endContainer.parentNode;
  14795. 14795   rng.endOffset = nodeIndex(endContainer) + 1;
  14796. 14796   }
  14797. 14797  
  14798. 14798   // Remove items between start/end
  14799. 14799   rangeUtils.walk(rng, function(nodes) {
  14800. 14800   each(nodes, function(node) {
  14801. 14801   process(node);
  14802. 14802  
  14803. 14803   // Remove parent span if it only contains text-decoration: underline, yet a parent node is also underlined.
  14804. 14804   if (node.nodeType === 1 && ed.dom.getStyle(node, 'text-decoration') === 'underline' && node.parentNode && getTextDecoration(node.parentNode) === 'underline') {
  14805. 14805   removeFormat({'deep': false, 'exact': true, 'inline': 'span', 'styles': {'textDecoration' : 'underline'}}, null, node);
  14806. 14806   }
  14807. 14807   });
  14808. 14808   });
  14809. 14809   };
  14810. 14810  
  14811. 14811   // Handle node
  14812. 14812   if (node) {
  14813. 14813   rng = dom.createRng();
  14814. 14814   rng.setStartBefore(node);
  14815. 14815   rng.setEndAfter(node);
  14816. 14816   removeRngStyle(rng);
  14817. 14817   return;
  14818. 14818   }
  14819. 14819  
  14820. 14820   if (!selection.isCollapsed() || !format.inline || dom.select('td.mceSelected,th.mceSelected').length) {
  14821. 14821   bookmark = selection.getBookmark();
  14822. 14822   removeRngStyle(selection.getRng(TRUE));
  14823. 14823   selection.moveToBookmark(bookmark);
  14824. 14824  
  14825. 14825   // Check if start element still has formatting then we are at: "<b>text|</b>text" and need to move the start into the next text node
  14826. 14826   if (match(name, vars, selection.getStart())) {
  14827. 14827   moveStart(selection.getRng(true));
  14828. 14828   }
  14829. 14829  
  14830. 14830   ed.nodeChanged();
  14831. 14831   } else
  14832. 14832   performCaretAction('remove', name, vars);
  14833. 14833   };
  14834. 14834  
  14835. 14835   function toggle(name, vars, node) {
  14836. 14836   var fmt = get(name);
  14837. 14837  
  14838. 14838   if (match(name, vars, node) && (!('toggle' in fmt[0]) || fmt[0]['toggle']))
  14839. 14839   remove(name, vars, node);
  14840. 14840   else
  14841. 14841   apply(name, vars, node);
  14842. 14842   };
  14843. 14843  
  14844. 14844   function matchNode(node, name, vars, similar) {
  14845. 14845   var formatList = get(name), format, i, classes;
  14846. 14846  
  14847. 14847   function matchItems(node, format, item_name) {
  14848. 14848   var key, value, items = format[item_name], i;
  14849. 14849  
  14850. 14850   // Check all items
  14851. 14851   if (items) {
  14852. 14852   // Non indexed object
  14853. 14853   if (items.length === undefined) {
  14854. 14854   for (key in items) {
  14855. 14855   if (items.hasOwnProperty(key)) {
  14856. 14856   if (item_name === 'attributes')
  14857. 14857   value = dom.getAttrib(node, key);
  14858. 14858   else
  14859. 14859   value = getStyle(node, key);
  14860. 14860  
  14861. 14861   if (similar && !value && !format.exact)
  14862. 14862   return;
  14863. 14863  
  14864. 14864   if ((!similar || format.exact) && !isEq(value, replaceVars(items[key], vars)))
  14865. 14865   return;
  14866. 14866   }
  14867. 14867   }
  14868. 14868   } else {
  14869. 14869   // Only one match needed for indexed arrays
  14870. 14870   for (i = 0; i < items.length; i++) {
  14871. 14871   if (item_name === 'attributes' ? dom.getAttrib(node, items[i]) : getStyle(node, items[i]))
  14872. 14872   return format;
  14873. 14873   }
  14874. 14874   }
  14875. 14875   }
  14876. 14876  
  14877. 14877   return format;
  14878. 14878   };
  14879. 14879  
  14880. 14880   if (formatList && node) {
  14881. 14881   // Check each format in list
  14882. 14882   for (i = 0; i < formatList.length; i++) {
  14883. 14883   format = formatList[i];
  14884. 14884  
  14885. 14885   // Name name, attributes, styles and classes
  14886. 14886   if (matchName(node, format) && matchItems(node, format, 'attributes') && matchItems(node, format, 'styles')) {
  14887. 14887   // Match classes
  14888. 14888   if (classes = format.classes) {
  14889. 14889   for (i = 0; i < classes.length; i++) {
  14890. 14890   if (!dom.hasClass(node, classes[i]))
  14891. 14891   return;
  14892. 14892   }
  14893. 14893   }
  14894. 14894  
  14895. 14895   return format;
  14896. 14896   }
  14897. 14897   }
  14898. 14898   }
  14899. 14899   };
  14900. 14900  
  14901. 14901   function match(name, vars, node) {
  14902. 14902   var startNode, i;
  14903. 14903  
  14904. 14904   function matchParents(node) {
  14905. 14905   // Find first node with similar format settings
  14906. 14906   node = dom.getParent(node, function(node) {
  14907. 14907   return !!matchNode(node, name, vars, true);
  14908. 14908   });
  14909. 14909  
  14910. 14910   // Do an exact check on the similar format element
  14911. 14911   return matchNode(node, name, vars);
  14912. 14912   };
  14913. 14913  
  14914. 14914   // Check specified node
  14915. 14915   if (node)
  14916. 14916   return matchParents(node);
  14917. 14917  
  14918. 14918   // Check pending formats
  14919. 14919   if (selection.isCollapsed()) {
  14920. 14920   for (i = pendingFormats.apply.length - 1; i >= 0; i--) {
  14921. 14921   if (pendingFormats.apply[i].name == name)
  14922. 14922   return true;
  14923. 14923   }
  14924. 14924  
  14925. 14925   for (i = pendingFormats.remove.length - 1; i >= 0; i--) {
  14926. 14926   if (pendingFormats.remove[i].name == name)
  14927. 14927   return false;
  14928. 14928   }
  14929. 14929  
  14930. 14930   return matchParents(selection.getNode());
  14931. 14931   }
  14932. 14932  
  14933. 14933   // Check selected node
  14934. 14934   node = selection.getNode();
  14935. 14935   if (matchParents(node))
  14936. 14936   return TRUE;
  14937. 14937  
  14938. 14938   // Check start node if it's different
  14939. 14939   startNode = selection.getStart();
  14940. 14940   if (startNode != node) {
  14941. 14941   if (matchParents(startNode))
  14942. 14942   return TRUE;
  14943. 14943   }
  14944. 14944  
  14945. 14945   return FALSE;
  14946. 14946   };
  14947. 14947  
  14948. 14948   function matchAll(names, vars) {
  14949. 14949   var startElement, matchedFormatNames = [], checkedMap = {}, i, ni, name;
  14950. 14950  
  14951. 14951   // If the selection is collapsed then check pending formats
  14952. 14952   if (selection.isCollapsed()) {
  14953. 14953   for (ni = 0; ni < names.length; ni++) {
  14954. 14954   // If the name is to be removed, then stop it from being added
  14955. 14955   for (i = pendingFormats.remove.length - 1; i >= 0; i--) {
  14956. 14956   name = names[ni];
  14957. 14957  
  14958. 14958   if (pendingFormats.remove[i].name == name) {
  14959. 14959   checkedMap[name] = true;
  14960. 14960   break;
  14961. 14961   }
  14962. 14962   }
  14963. 14963   }
  14964. 14964  
  14965. 14965   // If the format is to be applied
  14966. 14966   for (i = pendingFormats.apply.length - 1; i >= 0; i--) {
  14967. 14967   for (ni = 0; ni < names.length; ni++) {
  14968. 14968   name = names[ni];
  14969. 14969  
  14970. 14970   if (!checkedMap[name] && pendingFormats.apply[i].name == name) {
  14971. 14971   checkedMap[name] = true;
  14972. 14972   matchedFormatNames.push(name);
  14973. 14973   }
  14974. 14974   }
  14975. 14975   }
  14976. 14976   }
  14977. 14977  
  14978. 14978   // Check start of selection for formats
  14979. 14979   startElement = selection.getStart();
  14980. 14980   dom.getParent(startElement, function(node) {
  14981. 14981   var i, name;
  14982. 14982  
  14983. 14983   for (i = 0; i < names.length; i++) {
  14984. 14984   name = names[i];
  14985. 14985  
  14986. 14986   if (!checkedMap[name] && matchNode(node, name, vars)) {
  14987. 14987   checkedMap[name] = true;
  14988. 14988   matchedFormatNames.push(name);
  14989. 14989   }
  14990. 14990   }
  14991. 14991   });
  14992. 14992  
  14993. 14993   return matchedFormatNames;
  14994. 14994   };
  14995. 14995  
  14996. 14996   function canApply(name) {
  14997. 14997   var formatList = get(name), startNode, parents, i, x, selector;
  14998. 14998  
  14999. 14999   if (formatList) {
  15000. 15000   startNode = selection.getStart();
  15001. 15001   parents = getParents(startNode);
  15002. 15002  
  15003. 15003   for (x = formatList.length - 1; x >= 0; x--) {
  15004. 15004   selector = formatList[x].selector;
  15005. 15005  
  15006. 15006   // Format is not selector based, then always return TRUE
  15007. 15007   if (!selector)
  15008. 15008   return TRUE;
  15009. 15009  
  15010. 15010   for (i = parents.length - 1; i >= 0; i--) {
  15011. 15011   if (dom.is(parents[i], selector))
  15012. 15012   return TRUE;
  15013. 15013   }
  15014. 15014   }
  15015. 15015   }
  15016. 15016  
  15017. 15017   return FALSE;
  15018. 15018   };
  15019. 15019  
  15020. 15020   // Expose to public
  15021. 15021   tinymce.extend(this, {
  15022. 15022   get : get,
  15023. 15023   register : register,
  15024. 15024   apply : apply,
  15025. 15025   remove : remove,
  15026. 15026   toggle : toggle,
  15027. 15027   match : match,
  15028. 15028   matchAll : matchAll,
  15029. 15029   matchNode : matchNode,
  15030. 15030   canApply : canApply
  15031. 15031   });
  15032. 15032  
  15033. 15033   // Private functions
  15034. 15034  
  15035. 15035   function matchName(node, format) {
  15036. 15036   // Check for inline match
  15037. 15037   if (isEq(node, format.inline))
  15038. 15038   return TRUE;
  15039. 15039  
  15040. 15040   // Check for block match
  15041. 15041   if (isEq(node, format.block))
  15042. 15042   return TRUE;
  15043. 15043  
  15044. 15044   // Check for selector match
  15045. 15045   if (format.selector)
  15046. 15046   return dom.is(node, format.selector);
  15047. 15047   };
  15048. 15048  
  15049. 15049   function isEq(str1, str2) {
  15050. 15050   str1 = str1 || '';
  15051. 15051   str2 = str2 || '';
  15052. 15052  
  15053. 15053   str1 = '' + (str1.nodeName || str1);
  15054. 15054   str2 = '' + (str2.nodeName || str2);
  15055. 15055  
  15056. 15056   return str1.toLowerCase() == str2.toLowerCase();
  15057. 15057   };
  15058. 15058  
  15059. 15059   function getStyle(node, name) {
  15060. 15060   var styleVal = dom.getStyle(node, name);
  15061. 15061  
  15062. 15062   // Force the format to hex
  15063. 15063   if (name == 'color' || name == 'backgroundColor')
  15064. 15064   styleVal = dom.toHex(styleVal);
  15065. 15065  
  15066. 15066   // Opera will return bold as 700
  15067. 15067   if (name == 'fontWeight' && styleVal == 700)
  15068. 15068   styleVal = 'bold';
  15069. 15069  
  15070. 15070   return '' + styleVal;
  15071. 15071   };
  15072. 15072  
  15073. 15073   function replaceVars(value, vars) {
  15074. 15074   if (typeof(value) != "string")
  15075. 15075   value = value(vars);
  15076. 15076   else if (vars) {
  15077. 15077   value = value.replace(/%(\w+)/g, function(str, name) {
  15078. 15078   return vars[name] || str;
  15079. 15079   });
  15080. 15080   }
  15081. 15081  
  15082. 15082   return value;
  15083. 15083   };
  15084. 15084  
  15085. 15085   function isWhiteSpaceNode(node) {
  15086. 15086   return node && node.nodeType === 3 && /^([\s\r\n]+|)$/.test(node.nodeValue);
  15087. 15087   };
  15088. 15088  
  15089. 15089   function wrap(node, name, attrs) {
  15090. 15090   var wrapper = dom.create(name, attrs);
  15091. 15091  
  15092. 15092   node.parentNode.insertBefore(wrapper, node);
  15093. 15093   wrapper.appendChild(node);
  15094. 15094  
  15095. 15095   return wrapper;
  15096. 15096   };
  15097. 15097  
  15098. 15098   function expandRng(rng, format, remove) {
  15099. 15099   var startContainer = rng.startContainer,
  15100. 15100   startOffset = rng.startOffset,
  15101. 15101   endContainer = rng.endContainer,
  15102. 15102   endOffset = rng.endOffset, sibling, lastIdx, leaf;
  15103. 15103  
  15104. 15104   // This function walks up the tree if there is no siblings before/after the node
  15105. 15105   function findParentContainer(container, child_name, sibling_name, root) {
  15106. 15106   var parent, child;
  15107. 15107  
  15108. 15108   root = root || dom.getRoot();
  15109. 15109  
  15110. 15110   for (;;) {
  15111. 15111   // Check if we can move up are we at root level or body level
  15112. 15112   parent = container.parentNode;
  15113. 15113  
  15114. 15114   // Stop expanding on block elements or root depending on format
  15115. 15115   if (parent == root || (!format[0].block_expand && isBlock(parent)))
  15116. 15116   return container;
  15117. 15117  
  15118. 15118   for (sibling = parent[child_name]; sibling && sibling != container; sibling = sibling[sibling_name]) {
  15119. 15119   if (sibling.nodeType == 1 && !isBookmarkNode(sibling))
  15120. 15120   return container;
  15121. 15121  
  15122. 15122   if (sibling.nodeType == 3 && !isWhiteSpaceNode(sibling))
  15123. 15123   return container;
  15124. 15124   }
  15125. 15125  
  15126. 15126   container = container.parentNode;
  15127. 15127   }
  15128. 15128  
  15129. 15129   return container;
  15130. 15130   };
  15131. 15131  
  15132. 15132   // This function walks down the tree to find the leaf at the selection.
  15133. 15133   // The offset is also returned as if node initially a leaf, the offset may be in the middle of the text node.
  15134. 15134   function findLeaf(node, offset) {
  15135. 15135   if (offset === undefined)
  15136. 15136   offset = node.nodeType === 3 ? node.length : node.childNodes.length;
  15137. 15137   while (node && node.hasChildNodes()) {
  15138. 15138   node = node.childNodes[offset];
  15139. 15139   if (node)
  15140. 15140   offset = node.nodeType === 3 ? node.length : node.childNodes.length;
  15141. 15141   }
  15142. 15142   return { node: node, offset: offset };
  15143. 15143   }
  15144. 15144  
  15145. 15145   // If index based start position then resolve it
  15146. 15146   if (startContainer.nodeType == 1 && startContainer.hasChildNodes()) {
  15147. 15147   lastIdx = startContainer.childNodes.length - 1;
  15148. 15148   startContainer = startContainer.childNodes[startOffset > lastIdx ? lastIdx : startOffset];
  15149. 15149  
  15150. 15150   if (startContainer.nodeType == 3)
  15151. 15151   startOffset = 0;
  15152. 15152   }
  15153. 15153  
  15154. 15154   // If index based end position then resolve it
  15155. 15155   if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) {
  15156. 15156   lastIdx = endContainer.childNodes.length - 1;
  15157. 15157   endContainer = endContainer.childNodes[endOffset > lastIdx ? lastIdx : endOffset - 1];
  15158. 15158  
  15159. 15159   if (endContainer.nodeType == 3)
  15160. 15160   endOffset = endContainer.nodeValue.length;
  15161. 15161   }
  15162. 15162  
  15163. 15163   // Exclude bookmark nodes if possible
  15164. 15164   if (isBookmarkNode(startContainer.parentNode))
  15165. 15165   startContainer = startContainer.parentNode;
  15166. 15166  
  15167. 15167   if (isBookmarkNode(startContainer))
  15168. 15168   startContainer = startContainer.nextSibling || startContainer;
  15169. 15169  
  15170. 15170   if (isBookmarkNode(endContainer.parentNode)) {
  15171. 15171   endOffset = dom.nodeIndex(endContainer);
  15172. 15172   endContainer = endContainer.parentNode;
  15173. 15173   }
  15174. 15174  
  15175. 15175   if (isBookmarkNode(endContainer) && endContainer.previousSibling) {
  15176. 15176   endContainer = endContainer.previousSibling;
  15177. 15177   endOffset = endContainer.length;
  15178. 15178   }
  15179. 15179  
  15180. 15180   if (format[0].inline) {
  15181. 15181   // Avoid applying formatting to a trailing space.
  15182. 15182   leaf = findLeaf(endContainer, endOffset);
  15183. 15183   if (leaf.node) {
  15184. 15184   while (leaf.node && leaf.offset === 0 && leaf.node.previousSibling)
  15185. 15185   leaf = findLeaf(leaf.node.previousSibling);
  15186. 15186  
  15187. 15187   if (leaf.node && leaf.offset > 0 && leaf.node.nodeType === 3 &&
  15188. 15188   leaf.node.nodeValue.charAt(leaf.offset - 1) === ' ') {
  15189. 15189  
  15190. 15190   if (leaf.offset > 1) {
  15191. 15191   endContainer = leaf.node;
  15192. 15192   endContainer.splitText(leaf.offset - 1);
  15193. 15193   } else if (leaf.node.previousSibling) {
  15194. 15194   endContainer = leaf.node.previousSibling;
  15195. 15195   }
  15196. 15196   }
  15197. 15197   }
  15198. 15198   }
  15199. 15199  
  15200. 15200   // Move start/end point up the tree if the leaves are sharp and if we are in different containers
  15201. 15201   // Example * becomes !: !<p><b><i>*text</i><i>text*</i></b></p>!
  15202. 15202   // This will reduce the number of wrapper elements that needs to be created
  15203. 15203   // Move start point up the tree
  15204. 15204   if (format[0].inline || format[0].block_expand) {
  15205. 15205   startContainer = findParentContainer(startContainer, 'firstChild', 'nextSibling');
  15206. 15206   endContainer = findParentContainer(endContainer, 'lastChild', 'previousSibling');
  15207. 15207   }
  15208. 15208  
  15209. 15209   // Expand start/end container to matching selector
  15210. 15210   if (format[0].selector && format[0].expand !== FALSE && !format[0].inline) {
  15211. 15211   function findSelectorEndPoint(container, sibling_name) {
  15212. 15212   var parents, i, y, curFormat;
  15213. 15213  
  15214. 15214   if (container.nodeType == 3 && container.nodeValue.length == 0 && container[sibling_name])
  15215. 15215   container = container[sibling_name];
  15216. 15216  
  15217. 15217   parents = getParents(container);
  15218. 15218   for (i = 0; i < parents.length; i++) {
  15219. 15219   for (y = 0; y < format.length; y++) {
  15220. 15220   curFormat = format[y];
  15221. 15221  
  15222. 15222   // If collapsed state is set then skip formats that doesn't match that
  15223. 15223   if ("collapsed" in curFormat && curFormat.collapsed !== rng.collapsed)
  15224. 15224   continue;
  15225. 15225  
  15226. 15226   if (dom.is(parents[i], curFormat.selector))
  15227. 15227   return parents[i];
  15228. 15228   }
  15229. 15229   }
  15230. 15230  
  15231. 15231   return container;
  15232. 15232   };
  15233. 15233  
  15234. 15234   // Find new startContainer/endContainer if there is better one
  15235. 15235   startContainer = findSelectorEndPoint(startContainer, 'previousSibling');
  15236. 15236   endContainer = findSelectorEndPoint(endContainer, 'nextSibling');
  15237. 15237   }
  15238. 15238  
  15239. 15239   // Expand start/end container to matching block element or text node
  15240. 15240   if (format[0].block || format[0].selector) {
  15241. 15241   function findBlockEndPoint(container, sibling_name, sibling_name2) {
  15242. 15242   var node;
  15243. 15243  
  15244. 15244   // Expand to block of similar type
  15245. 15245   if (!format[0].wrapper)
  15246. 15246   node = dom.getParent(container, format[0].block);
  15247. 15247  
  15248. 15248   // Expand to first wrappable block element or any block element
  15249. 15249   if (!node)
  15250. 15250   node = dom.getParent(container.nodeType == 3 ? container.parentNode : container, isBlock);
  15251. 15251  
  15252. 15252   // Exclude inner lists from wrapping
  15253. 15253   if (node && format[0].wrapper)
  15254. 15254   node = getParents(node, 'ul,ol').reverse()[0] || node;
  15255. 15255  
  15256. 15256   // Didn't find a block element look for first/last wrappable element
  15257. 15257   if (!node) {
  15258. 15258   node = container;
  15259. 15259  
  15260. 15260   while (node[sibling_name] && !isBlock(node[sibling_name])) {
  15261. 15261   node = node[sibling_name];
  15262. 15262  
  15263. 15263   // Break on BR but include it will be removed later on
  15264. 15264   // we can't remove it now since we need to check if it can be wrapped
  15265. 15265   if (isEq(node, 'br'))
  15266. 15266   break;
  15267. 15267   }
  15268. 15268   }
  15269. 15269  
  15270. 15270   return node || container;
  15271. 15271   };
  15272. 15272  
  15273. 15273   // Find new startContainer/endContainer if there is better one
  15274. 15274   startContainer = findBlockEndPoint(startContainer, 'previousSibling');
  15275. 15275   endContainer = findBlockEndPoint(endContainer, 'nextSibling');
  15276. 15276  
  15277. 15277   // Non block element then try to expand up the leaf
  15278. 15278   if (format[0].block) {
  15279. 15279   if (!isBlock(startContainer))
  15280. 15280   startContainer = findParentContainer(startContainer, 'firstChild', 'nextSibling');
  15281. 15281  
  15282. 15282   if (!isBlock(endContainer))
  15283. 15283   endContainer = findParentContainer(endContainer, 'lastChild', 'previousSibling');
  15284. 15284   }
  15285. 15285   }
  15286. 15286  
  15287. 15287   // Setup index for startContainer
  15288. 15288   if (startContainer.nodeType == 1) {
  15289. 15289   startOffset = nodeIndex(startContainer);
  15290. 15290   startContainer = startContainer.parentNode;
  15291. 15291   }
  15292. 15292  
  15293. 15293   // Setup index for endContainer
  15294. 15294   if (endContainer.nodeType == 1) {
  15295. 15295   endOffset = nodeIndex(endContainer) + 1;
  15296. 15296   endContainer = endContainer.parentNode;
  15297. 15297   }
  15298. 15298  
  15299. 15299   // Return new range like object
  15300. 15300   return {
  15301. 15301   startContainer : startContainer,
  15302. 15302   startOffset : startOffset,
  15303. 15303   endContainer : endContainer,
  15304. 15304   endOffset : endOffset
  15305. 15305   };
  15306. 15306   }
  15307. 15307  
  15308. 15308   function removeFormat(format, vars, node, compare_node) {
  15309. 15309   var i, attrs, stylesModified;
  15310. 15310  
  15311. 15311   // Check if node matches format
  15312. 15312   if (!matchName(node, format))
  15313. 15313   return FALSE;
  15314. 15314  
  15315. 15315   // Should we compare with format attribs and styles
  15316. 15316   if (format.remove != 'all') {
  15317. 15317   // Remove styles
  15318. 15318   each(format.styles, function(value, name) {
  15319. 15319   value = replaceVars(value, vars);
  15320. 15320  
  15321. 15321   // Indexed array
  15322. 15322   if (typeof(name) === 'number') {
  15323. 15323   name = value;
  15324. 15324   compare_node = 0;
  15325. 15325   }
  15326. 15326  
  15327. 15327   if (!compare_node || isEq(getStyle(compare_node, name), value))
  15328. 15328   dom.setStyle(node, name, '');
  15329. 15329  
  15330. 15330   stylesModified = 1;
  15331. 15331   });
  15332. 15332  
  15333. 15333   // Remove style attribute if it's empty
  15334. 15334   if (stylesModified && dom.getAttrib(node, 'style') == '') {
  15335. 15335   node.removeAttribute('style');
  15336. 15336   node.removeAttribute('data-mce-style');
  15337. 15337   }
  15338. 15338  
  15339. 15339   // Remove attributes
  15340. 15340   each(format.attributes, function(value, name) {
  15341. 15341   var valueOut;
  15342. 15342  
  15343. 15343   value = replaceVars(value, vars);
  15344. 15344  
  15345. 15345   // Indexed array
  15346. 15346   if (typeof(name) === 'number') {
  15347. 15347   name = value;
  15348. 15348   compare_node = 0;
  15349. 15349   }
  15350. 15350  
  15351. 15351   if (!compare_node || isEq(dom.getAttrib(compare_node, name), value)) {
  15352. 15352   // Keep internal classes
  15353. 15353   if (name == 'class') {
  15354. 15354   value = dom.getAttrib(node, name);
  15355. 15355   if (value) {
  15356. 15356   // Build new class value where everything is removed except the internal prefixed classes
  15357. 15357   valueOut = '';
  15358. 15358   each(value.split(/\s+/), function(cls) {
  15359. 15359   if (/mce\w+/.test(cls))
  15360. 15360   valueOut += (valueOut ? ' ' : '') + cls;
  15361. 15361   });
  15362. 15362  
  15363. 15363   // We got some internal classes left
  15364. 15364   if (valueOut) {
  15365. 15365   dom.setAttrib(node, name, valueOut);
  15366. 15366   return;
  15367. 15367   }
  15368. 15368   }
  15369. 15369   }
  15370. 15370  
  15371. 15371   // IE6 has a bug where the attribute doesn't get removed correctly
  15372. 15372   if (name == "class")
  15373. 15373   node.removeAttribute('className');
  15374. 15374  
  15375. 15375   // Remove mce prefixed attributes
  15376. 15376   if (MCE_ATTR_RE.test(name))
  15377. 15377   node.removeAttribute('data-mce-' + name);
  15378. 15378  
  15379. 15379   node.removeAttribute(name);
  15380. 15380   }
  15381. 15381   });
  15382. 15382  
  15383. 15383   // Remove classes
  15384. 15384   each(format.classes, function(value) {
  15385. 15385   value = replaceVars(value, vars);
  15386. 15386  
  15387. 15387   if (!compare_node || dom.hasClass(compare_node, value))
  15388. 15388   dom.removeClass(node, value);
  15389. 15389   });
  15390. 15390  
  15391. 15391   // Check for non internal attributes
  15392. 15392   attrs = dom.getAttribs(node);
  15393. 15393   for (i = 0; i < attrs.length; i++) {
  15394. 15394   if (attrs[i].nodeName.indexOf('_') !== 0)
  15395. 15395   return FALSE;
  15396. 15396   }
  15397. 15397   }
  15398. 15398  
  15399. 15399   // Remove the inline child if it's empty for example <b> or <span>
  15400. 15400   if (format.remove != 'none') {
  15401. 15401   removeNode(node, format);
  15402. 15402   return TRUE;
  15403. 15403   }
  15404. 15404   };
  15405. 15405  
  15406. 15406   function removeNode(node, format) {
  15407. 15407   var parentNode = node.parentNode, rootBlockElm;
  15408. 15408  
  15409. 15409   if (format.block) {
  15410. 15410   if (!forcedRootBlock) {
  15411. 15411   function find(node, next, inc) {
  15412. 15412   node = getNonWhiteSpaceSibling(node, next, inc);
  15413. 15413  
  15414. 15414   return !node || (node.nodeName == 'BR' || isBlock(node));
  15415. 15415   };
  15416. 15416  
  15417. 15417   // Append BR elements if needed before we remove the block
  15418. 15418   if (isBlock(node) && !isBlock(parentNode)) {
  15419. 15419   if (!find(node, FALSE) && !find(node.firstChild, TRUE, 1))
  15420. 15420   node.insertBefore(dom.create('br'), node.firstChild);
  15421. 15421  
  15422. 15422   if (!find(node, TRUE) && !find(node.lastChild, FALSE, 1))
  15423. 15423   node.appendChild(dom.create('br'));
  15424. 15424   }
  15425. 15425   } else {
  15426. 15426   // Wrap the block in a forcedRootBlock if we are at the root of document
  15427. 15427   if (parentNode == dom.getRoot()) {
  15428. 15428   if (!format.list_block || !isEq(node, format.list_block)) {
  15429. 15429   each(tinymce.grep(node.childNodes), function(node) {
  15430. 15430   if (isValid(forcedRootBlock, node.nodeName.toLowerCase())) {
  15431. 15431   if (!rootBlockElm)
  15432. 15432   rootBlockElm = wrap(node, forcedRootBlock);
  15433. 15433   else
  15434. 15434   rootBlockElm.appendChild(node);
  15435. 15435   } else
  15436. 15436   rootBlockElm = 0;
  15437. 15437   });
  15438. 15438   }
  15439. 15439   }
  15440. 15440   }
  15441. 15441   }
  15442. 15442  
  15443. 15443   // Never remove nodes that isn't the specified inline element if a selector is specified too
  15444. 15444   if (format.selector && format.inline && !isEq(format.inline, node))
  15445. 15445   return;
  15446. 15446  
  15447. 15447   dom.remove(node, 1);
  15448. 15448   };
  15449. 15449  
  15450. 15450   function getNonWhiteSpaceSibling(node, next, inc) {
  15451. 15451   if (node) {
  15452. 15452   next = next ? 'nextSibling' : 'previousSibling';
  15453. 15453  
  15454. 15454   for (node = inc ? node : node[next]; node; node = node[next]) {
  15455. 15455   if (node.nodeType == 1 || !isWhiteSpaceNode(node))
  15456. 15456   return node;
  15457. 15457   }
  15458. 15458   }
  15459. 15459   };
  15460. 15460  
  15461. 15461   function isBookmarkNode(node) {
  15462. 15462   return node && node.nodeType == 1 && node.getAttribute('data-mce-type') == 'bookmark';
  15463. 15463   };
  15464. 15464  
  15465. 15465   function mergeSiblings(prev, next) {
  15466. 15466   var marker, sibling, tmpSibling;
  15467. 15467  
  15468. 15468   function compareElements(node1, node2) {
  15469. 15469   // Not the same name
  15470. 15470   if (node1.nodeName != node2.nodeName)
  15471. 15471   return FALSE;
  15472. 15472  
  15473. 15473   function getAttribs(node) {
  15474. 15474   var attribs = {};
  15475. 15475  
  15476. 15476   each(dom.getAttribs(node), function(attr) {
  15477. 15477   var name = attr.nodeName.toLowerCase();
  15478. 15478  
  15479. 15479   // Don't compare internal attributes or style
  15480. 15480   if (name.indexOf('_') !== 0 && name !== 'style')
  15481. 15481   attribs[name] = dom.getAttrib(node, name);
  15482. 15482   });
  15483. 15483  
  15484. 15484   return attribs;
  15485. 15485   };
  15486. 15486  
  15487. 15487   function compareObjects(obj1, obj2) {
  15488. 15488   var value, name;
  15489. 15489  
  15490. 15490   for (name in obj1) {
  15491. 15491   // Obj1 has item obj2 doesn't have
  15492. 15492   if (obj1.hasOwnProperty(name)) {
  15493. 15493   value = obj2[name];
  15494. 15494  
  15495. 15495   // Obj2 doesn't have obj1 item
  15496. 15496   if (value === undefined)
  15497. 15497   return FALSE;
  15498. 15498  
  15499. 15499   // Obj2 item has a different value
  15500. 15500   if (obj1[name] != value)
  15501. 15501   return FALSE;
  15502. 15502  
  15503. 15503   // Delete similar value
  15504. 15504   delete obj2[name];
  15505. 15505   }
  15506. 15506   }
  15507. 15507  
  15508. 15508   // Check if obj 2 has something obj 1 doesn't have
  15509. 15509   for (name in obj2) {
  15510. 15510   // Obj2 has item obj1 doesn't have
  15511. 15511   if (obj2.hasOwnProperty(name))
  15512. 15512   return FALSE;
  15513. 15513   }
  15514. 15514  
  15515. 15515   return TRUE;
  15516. 15516   };
  15517. 15517  
  15518. 15518   // Attribs are not the same
  15519. 15519   if (!compareObjects(getAttribs(node1), getAttribs(node2)))
  15520. 15520   return FALSE;
  15521. 15521  
  15522. 15522   // Styles are not the same
  15523. 15523   if (!compareObjects(dom.parseStyle(dom.getAttrib(node1, 'style')), dom.parseStyle(dom.getAttrib(node2, 'style'))))
  15524. 15524   return FALSE;
  15525. 15525  
  15526. 15526   return TRUE;
  15527. 15527   };
  15528. 15528  
  15529. 15529   // Check if next/prev exists and that they are elements
  15530. 15530   if (prev && next) {
  15531. 15531   function findElementSibling(node, sibling_name) {
  15532. 15532   for (sibling = node; sibling; sibling = sibling[sibling_name]) {
  15533. 15533   if (sibling.nodeType == 3 && sibling.nodeValue.length !== 0)
  15534. 15534   return node;
  15535. 15535  
  15536. 15536   if (sibling.nodeType == 1 && !isBookmarkNode(sibling))
  15537. 15537   return sibling;
  15538. 15538   }
  15539. 15539  
  15540. 15540   return node;
  15541. 15541   };
  15542. 15542  
  15543. 15543   // If previous sibling is empty then jump over it
  15544. 15544   prev = findElementSibling(prev, 'previousSibling');
  15545. 15545   next = findElementSibling(next, 'nextSibling');
  15546. 15546  
  15547. 15547   // Compare next and previous nodes
  15548. 15548   if (compareElements(prev, next)) {
  15549. 15549   // Append nodes between
  15550. 15550   for (sibling = prev.nextSibling; sibling && sibling != next;) {
  15551. 15551   tmpSibling = sibling;
  15552. 15552   sibling = sibling.nextSibling;
  15553. 15553   prev.appendChild(tmpSibling);
  15554. 15554   }
  15555. 15555  
  15556. 15556   // Remove next node
  15557. 15557   dom.remove(next);
  15558. 15558  
  15559. 15559   // Move children into prev node
  15560. 15560   each(tinymce.grep(next.childNodes), function(node) {
  15561. 15561   prev.appendChild(node);
  15562. 15562   });
  15563. 15563  
  15564. 15564   return prev;
  15565. 15565   }
  15566. 15566   }
  15567. 15567  
  15568. 15568   return next;
  15569. 15569   };
  15570. 15570  
  15571. 15571   function isTextBlock(name) {
  15572. 15572   return /^(h[1-6]|p|div|pre|address|dl|dt|dd)$/.test(name);
  15573. 15573   };
  15574. 15574  
  15575. 15575   function getContainer(rng, start) {
  15576. 15576   var container, offset, lastIdx;
  15577. 15577  
  15578. 15578   container = rng[start ? 'startContainer' : 'endContainer'];
  15579. 15579   offset = rng[start ? 'startOffset' : 'endOffset'];
  15580. 15580  
  15581. 15581   if (container.nodeType == 1) {
  15582. 15582   lastIdx = container.childNodes.length - 1;
  15583. 15583  
  15584. 15584   if (!start && offset)
  15585. 15585   offset--;
  15586. 15586  
  15587. 15587   container = container.childNodes[offset > lastIdx ? lastIdx : offset];
  15588. 15588   }
  15589. 15589  
  15590. 15590   return container;
  15591. 15591   };
  15592. 15592  
  15593. 15593   function performCaretAction(type, name, vars) {
  15594. 15594   var i, currentPendingFormats = pendingFormats[type],
  15595. 15595   otherPendingFormats = pendingFormats[type == 'apply' ? 'remove' : 'apply'];
  15596. 15596  
  15597. 15597   function hasPending() {
  15598. 15598   return pendingFormats.apply.length || pendingFormats.remove.length;
  15599. 15599   };
  15600. 15600  
  15601. 15601   function resetPending() {
  15602. 15602   pendingFormats.apply = [];
  15603. 15603   pendingFormats.remove = [];
  15604. 15604   };
  15605. 15605  
  15606. 15606   function perform(caret_node) {
  15607. 15607   // Apply pending formats
  15608. 15608   each(pendingFormats.apply.reverse(), function(item) {
  15609. 15609   apply(item.name, item.vars, caret_node);
  15610. 15610  
  15611. 15611   // Colored nodes should be underlined so that the color of the underline matches the text color.
  15612. 15612   if (item.name === 'forecolor' && item.vars.value)
  15613. 15613   processUnderlineAndColor(caret_node.parentNode);
  15614. 15614   });
  15615. 15615  
  15616. 15616   // Remove pending formats
  15617. 15617   each(pendingFormats.remove.reverse(), function(item) {
  15618. 15618   remove(item.name, item.vars, caret_node);
  15619. 15619   });
  15620. 15620  
  15621. 15621   dom.remove(caret_node, 1);
  15622. 15622   resetPending();
  15623. 15623   };
  15624. 15624  
  15625. 15625   // Check if it already exists then ignore it
  15626. 15626   for (i = currentPendingFormats.length - 1; i >= 0; i--) {
  15627. 15627   if (currentPendingFormats[i].name == name)
  15628. 15628   return;
  15629. 15629   }
  15630. 15630  
  15631. 15631   currentPendingFormats.push({name : name, vars : vars});
  15632. 15632  
  15633. 15633   // Check if it's in the other type, then remove it
  15634. 15634   for (i = otherPendingFormats.length - 1; i >= 0; i--) {
  15635. 15635   if (otherPendingFormats[i].name == name)
  15636. 15636   otherPendingFormats.splice(i, 1);
  15637. 15637   }
  15638. 15638  
  15639. 15639   // Pending apply or remove formats
  15640. 15640   if (hasPending()) {
  15641. 15641   ed.getDoc().execCommand('FontName', false, 'mceinline');
  15642. 15642   pendingFormats.lastRng = selection.getRng();
  15643. 15643  
  15644. 15644   // IE will convert the current word
  15645. 15645   each(dom.select('font,span'), function(node) {
  15646. 15646   var bookmark;
  15647. 15647  
  15648. 15648   if (isCaretNode(node)) {
  15649. 15649   bookmark = selection.getBookmark();
  15650. 15650   perform(node);
  15651. 15651   selection.moveToBookmark(bookmark);
  15652. 15652   ed.nodeChanged();
  15653. 15653   }
  15654. 15654   });
  15655. 15655  
  15656. 15656   // Only register listeners once if we need to
  15657. 15657   if (!pendingFormats.isListening && hasPending()) {
  15658. 15658   pendingFormats.isListening = true;
  15659. 15659  
  15660. 15660   each('onKeyDown,onKeyUp,onKeyPress,onMouseUp'.split(','), function(event) {
  15661. 15661   ed[event].addToTop(function(ed, e) {
  15662. 15662   // Do we have pending formats and is the selection moved has moved
  15663. 15663   if (hasPending() && !tinymce.dom.RangeUtils.compareRanges(pendingFormats.lastRng, selection.getRng())) {
  15664. 15664   each(dom.select('font,span'), function(node) {
  15665. 15665   var textNode, rng;
  15666. 15666  
  15667. 15667   // Look for marker
  15668. 15668   if (isCaretNode(node)) {
  15669. 15669   textNode = node.firstChild;
  15670. 15670  
  15671. 15671   if (textNode) {
  15672. 15672   perform(node);
  15673. 15673  
  15674. 15674   rng = dom.createRng();
  15675. 15675   rng.setStart(textNode, textNode.nodeValue.length);
  15676. 15676   rng.setEnd(textNode, textNode.nodeValue.length);
  15677. 15677   selection.setRng(rng);
  15678. 15678   ed.nodeChanged();
  15679. 15679   } else
  15680. 15680   dom.remove(node);
  15681. 15681   }
  15682. 15682   });
  15683. 15683  
  15684. 15684   // Always unbind and clear pending styles on keyup
  15685. 15685   if (e.type == 'keyup' || e.type == 'mouseup')
  15686. 15686   resetPending();
  15687. 15687   }
  15688. 15688   });
  15689. 15689   });
  15690. 15690   }
  15691. 15691   }
  15692. 15692   };
  15693. 15693   };
  15694. 15694  })(tinymce);
  15695. 15695  
  15696. 15696  tinymce.onAddEditor.add(function(tinymce, ed) {
  15697. 15697   var filters, fontSizes, dom, settings = ed.settings;
  15698. 15698  
  15699. 15699   if (settings.inline_styles) {
  15700. 15700   fontSizes = tinymce.explode(settings.font_size_style_values);
  15701. 15701  
  15702. 15702   function replaceWithSpan(node, styles) {
  15703. 15703   tinymce.each(styles, function(value, name) {
  15704. 15704   if (value)
  15705. 15705   dom.setStyle(node, name, value);
  15706. 15706   });
  15707. 15707  
  15708. 15708   dom.rename(node, 'span');
  15709. 15709   };
  15710. 15710  
  15711. 15711   filters = {
  15712. 15712   font : function(dom, node) {
  15713. 15713   replaceWithSpan(node, {
  15714. 15714   backgroundColor : node.style.backgroundColor,
  15715. 15715   color : node.color,
  15716. 15716   fontFamily : node.face,
  15717. 15717   fontSize : fontSizes[parseInt(node.size) - 1]
  15718. 15718   });
  15719. 15719   },
  15720. 15720  
  15721. 15721   u : function(dom, node) {
  15722. 15722   replaceWithSpan(node, {
  15723. 15723   textDecoration : 'underline'
  15724. 15724   });
  15725. 15725   },
  15726. 15726  
  15727. 15727   strike : function(dom, node) {
  15728. 15728   replaceWithSpan(node, {
  15729. 15729   textDecoration : 'line-through'
  15730. 15730   });
  15731. 15731   }
  15732. 15732   };
  15733. 15733  
  15734. 15734   function convert(editor, params) {
  15735. 15735   dom = editor.dom;
  15736. 15736  
  15737. 15737   if (settings.convert_fonts_to_spans) {
  15738. 15738   tinymce.each(dom.select('font,u,strike', params.node), function(node) {
  15739. 15739   filters[node.nodeName.toLowerCase()](ed.dom, node);
  15740. 15740   });
  15741. 15741   }
  15742. 15742   };
  15743. 15743  
  15744. 15744   ed.onPreProcess.add(convert);
  15745. 15745   ed.onSetContent.add(convert);
  15746. 15746  
  15747. 15747   ed.onInit.add(function() {
  15748. 15748   ed.selection.onSetContent.add(convert);
  15749. 15749   });
  15750. 15750   }
  15751. 15751  });